From 813c351607a6d29ca3ba3d63a29ba8947d1f03b5 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 16 Dec 2022 21:44:10 -0800 Subject: [PATCH 001/138] Fix breadcrumb tab item click area not extending to background height --- osu.Game/Graphics/UserInterface/BreadcrumbControl.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index 67b63e120b..fc0770d896 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -52,8 +52,8 @@ namespace osu.Game.Graphics.UserInterface public readonly SpriteIcon Chevron; - //don't allow clicking between transitions and don't make the chevron clickable - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Alpha == 1f && Text.ReceivePositionalInputAt(screenSpacePos); + //don't allow clicking between transitions + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Alpha == 1f && base.ReceivePositionalInputAt(screenSpacePos); public override bool HandleNonPositionalInput => State == Visibility.Visible; public override bool HandlePositionalInput => State == Visibility.Visible; @@ -95,7 +95,7 @@ namespace osu.Game.Graphics.UserInterface { Text.Font = Text.Font.With(size: 18); Text.Margin = new MarginPadding { Vertical = 8 }; - Padding = new MarginPadding { Right = padding + ChevronSize }; + Margin = new MarginPadding { Right = padding + ChevronSize }; Add(Chevron = new SpriteIcon { Anchor = Anchor.CentreRight, From c119d41a2dac3c8d50304aeb7b16484281404212 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Dec 2022 17:52:53 +0900 Subject: [PATCH 002/138] Only show song select for now at ui scale adjust first run screen Having both was a bit too much. Still not happy with this but it's a bit less sensory overload. I think while it's cool being able to show nested screens like this, it needs more thought to actually be a good experience. --- osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index 63688841d0..a3969883e0 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.FirstRunSetup Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.None, - Size = new Vector2(screen_width, screen_width / 16f * 9 / 2), + Size = new Vector2(screen_width, screen_width / 16f * 9), Children = new Drawable[] { new GridContainer @@ -68,7 +68,6 @@ namespace osu.Game.Overlays.FirstRunSetup { new Drawable[] { - new SampleScreenContainer(new PinnedMainMenu()), new SampleScreenContainer(new NestedSongSelect()), }, // TODO: add more screens here in the future (gameplay / results) @@ -109,17 +108,6 @@ namespace osu.Game.Overlays.FirstRunSetup public override bool? AllowTrackAdjustments => false; } - private partial class PinnedMainMenu : MainMenu - { - public override void OnEntering(ScreenTransitionEvent e) - { - base.OnEntering(e); - - Buttons.ReturnToTopOnIdle = false; - Buttons.State = ButtonSystemState.TopLevel; - } - } - private partial class UIScaleSlider : OsuSliderBar { public override LocalisableString TooltipText => base.TooltipText + "x"; From 439b8ac56ae50fdb77f8d8fd174925a1a6b52324 Mon Sep 17 00:00:00 2001 From: wiskerz76 Date: Mon, 19 Dec 2022 16:33:35 -0500 Subject: [PATCH 003/138] Fix file select popup getting stuck when switching first run screens while selecting Closes #21663 Supersedes #21724 --- .../FirstRunSetup/ScreenImportFromStable.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 04aa976ff1..8b85bb49a5 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -126,7 +126,8 @@ namespace osu.Game.Overlays.FirstRunSetup if (available) { - copyInformation.Text = "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation."; + copyInformation.Text = + "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation."; } else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; @@ -173,6 +174,18 @@ namespace osu.Game.Overlays.FirstRunSetup c.Current.Disabled = !allow; } + public override void OnSuspending(ScreenTransitionEvent e) + { + stableLocatorTextBox.HidePopover(); + base.OnSuspending(e); + } + + public override bool OnExiting(ScreenExitEvent e) + { + stableLocatorTextBox.HidePopover(); + return base.OnExiting(e); + } + private partial class ImportCheckbox : SettingsCheckbox { public readonly StableContent StableContent; From 2f0c772dcb4d5110727de6389db3f1e9c31d474c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Dec 2022 15:52:29 +0900 Subject: [PATCH 004/138] Add argon pro skin --- .../Overlays/Settings/Sections/SkinSection.cs | 1 + osu.Game/Skinning/ArgonProSkin.cs | 48 +++++++++++++++++++ osu.Game/Skinning/ArgonSkin.cs | 6 +-- osu.Game/Skinning/SkinInfo.cs | 1 + osu.Game/Skinning/SkinManager.cs | 1 + osu.Game/Skinning/SkinnableSprite.cs | 1 + 6 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Skinning/ArgonProSkin.cs diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 826a1e7404..f75656cc99 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -105,6 +105,7 @@ namespace osu.Game.Overlays.Settings.Sections dropdownItems.Clear(); dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.ARGON_SKIN).ToLive(realm)); + dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.ARGON_PRO_SKIN).ToLive(realm)); dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.TRIANGLES_SKIN).ToLive(realm)); dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.CLASSIC_SKIN).ToLive(realm)); diff --git a/osu.Game/Skinning/ArgonProSkin.cs b/osu.Game/Skinning/ArgonProSkin.cs new file mode 100644 index 0000000000..2bc8e55ec0 --- /dev/null +++ b/osu.Game/Skinning/ArgonProSkin.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Audio.Sample; +using osu.Game.Audio; +using osu.Game.Extensions; +using osu.Game.IO; + +namespace osu.Game.Skinning +{ + public class ArgonProSkin : ArgonSkin + { + public new static SkinInfo CreateInfo() => new SkinInfo + { + ID = Skinning.SkinInfo.ARGON_PRO_SKIN, + Name = "osu! \"argon\" pro (2022)", + Creator = "team osu!", + Protected = true, + InstantiationInfo = typeof(ArgonProSkin).GetInvariantInstantiationInfo() + }; + + public override ISample? GetSample(ISampleInfo sampleInfo) + { + foreach (string lookup in sampleInfo.LookupNames) + { + string remappedLookup = lookup.Replace("Gameplay/", "Gameplay/Pro/"); + + var sample = Samples?.Get(remappedLookup) ?? Resources.AudioManager?.Samples.Get(remappedLookup); + if (sample != null) + return sample; + } + + return null; + } + + public ArgonProSkin(IStorageResourceProvider resources) + : this(CreateInfo(), resources) + { + } + + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] + public ArgonProSkin(SkinInfo skin, IStorageResourceProvider resources) + : base(skin, resources) + { + } + } +} diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 6a0c4a23e5..d78147aaea 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -30,7 +30,7 @@ namespace osu.Game.Skinning InstantiationInfo = typeof(ArgonSkin).GetInvariantInstantiationInfo() }; - private readonly IStorageResourceProvider resources; + protected readonly IStorageResourceProvider Resources; public ArgonSkin(IStorageResourceProvider resources) : this(CreateInfo(), resources) @@ -41,7 +41,7 @@ namespace osu.Game.Skinning public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) : base(skin, resources) { - this.resources = resources; + Resources = resources; Configuration.CustomComboColours = new List { @@ -72,7 +72,7 @@ namespace osu.Game.Skinning { foreach (string lookup in sampleInfo.LookupNames) { - var sample = Samples?.Get(lookup) ?? resources.AudioManager?.Samples.Get(lookup); + var sample = Samples?.Get(lookup) ?? Resources.AudioManager?.Samples.Get(lookup); if (sample != null) return sample; } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 7b31c8fe88..9ad91f8725 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -20,6 +20,7 @@ namespace osu.Game.Skinning { internal static readonly Guid TRIANGLES_SKIN = new Guid("2991CFD8-2140-469A-BCB9-2EC23FBCE4AD"); internal static readonly Guid ARGON_SKIN = new Guid("CFFA69DE-B3E3-4DEE-8563-3C4F425C05D0"); + internal static readonly Guid ARGON_PRO_SKIN = new Guid("9FC9CF5D-0F16-4C71-8256-98868321AC43"); internal static readonly Guid CLASSIC_SKIN = new Guid("81F02CD3-EEC6-4865-AC23-FAE26A386187"); internal static readonly Guid RANDOM_SKIN = new Guid("D39DFEFB-477C-4372-B1EA-2BCEA5FB8908"); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 2ad62dbb61..f750bfad8a 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -84,6 +84,7 @@ namespace osu.Game.Skinning DefaultClassicSkin = new DefaultLegacySkin(this), trianglesSkin = new TrianglesSkin(this), argonSkin = new ArgonSkin(this), + new ArgonProSkin(this), }; // Ensure the default entries are present. diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 1a8a3a26c9..a66f3e0549 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -111,6 +111,7 @@ namespace osu.Game.Skinning // Temporarily used to exclude undesirable ISkin implementations static bool isUserSkin(ISkin skin) => skin.GetType() == typeof(TrianglesSkin) + || skin.GetType() == typeof(ArgonProSkin) || skin.GetType() == typeof(ArgonSkin) || skin.GetType() == typeof(DefaultLegacySkin) || skin.GetType() == typeof(LegacySkin); From f7c854f1b0ebd59bac44c175dd54694c90ec4b39 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Tue, 20 Dec 2022 21:18:32 +0900 Subject: [PATCH 005/138] Change asset folder --- osu.Game/Skinning/ArgonProSkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/ArgonProSkin.cs b/osu.Game/Skinning/ArgonProSkin.cs index 2bc8e55ec0..b753dd8fbe 100644 --- a/osu.Game/Skinning/ArgonProSkin.cs +++ b/osu.Game/Skinning/ArgonProSkin.cs @@ -24,7 +24,7 @@ namespace osu.Game.Skinning { foreach (string lookup in sampleInfo.LookupNames) { - string remappedLookup = lookup.Replace("Gameplay/", "Gameplay/Pro/"); + string remappedLookup = lookup.Replace(@"Gameplay/", @"Gameplay/ArgonPro/"); var sample = Samples?.Get(remappedLookup) ?? Resources.AudioManager?.Samples.Get(remappedLookup); if (sample != null) From 2c402d474035be888284b7318050aabb9abb3423 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Dec 2022 01:24:41 +0900 Subject: [PATCH 006/138] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8b4fa2dc6b..83dbf7e370 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From bf074adb137c2178eebd38dd285fbaec82c86364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Dec 2022 18:24:26 +0100 Subject: [PATCH 007/138] Remove unused using directive --- osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index a3969883e0..1bcb1bcdf4 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; -using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; From cebd5f6dc2384d6468f56f31385cda8e7c035d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Dec 2022 20:36:27 +0100 Subject: [PATCH 008/138] Fix restore default button having a minuscule hit area Another casualty of edc78205d5312e9278f2a22ef156fd34af492595. This particular button was actually *relying* on receiving positional events from its entire bounding box rather than `Content`, in order for the button to be htitable more easily, which broke as other buttons were fixed to behave more in line with expectations. Upon closer inspection this is another case of a weird carried-over construction. The button doesn't really need to inherit `OsuButton` or do any of the arcane stuff that it was doing, so it's now a plain `OsuClickableContainer` with less `Content` hackery. --- .../Overlays/RestoreDefaultValueButton.cs | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 24dec44588..9d5e5db6e6 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -7,18 +7,20 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK; namespace osu.Game.Overlays { - public partial class RestoreDefaultValueButton : OsuButton, IHasTooltip, IHasCurrentValue + public partial class RestoreDefaultValueButton : OsuClickableContainer, IHasCurrentValue { public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; @@ -51,15 +53,32 @@ namespace osu.Game.Overlays private const float size = 4; + private CircularContainer circle = null!; + private Box background = null!; + + public RestoreDefaultValueButton() + : base(HoverSampleSet.Button) + { + } + [BackgroundDependencyLoader] private void load(OsuColour colour) { - BackgroundColour = colour.Lime1; + // size intentionally much larger than actual drawn content, so that the button is easier to click. Size = new Vector2(3 * size); - Content.RelativeSizeAxes = Axes.None; - Content.Size = new Vector2(size); - Content.CornerRadius = size / 2; + Add(circle = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(size), + Masking = true, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colour.Lime1 + } + }); Alpha = 0f; @@ -77,7 +96,7 @@ namespace osu.Game.Overlays FinishTransforms(true); } - public LocalisableString TooltipText => "revert to default"; + public override LocalisableString TooltipText => "revert to default"; protected override bool OnHover(HoverEvent e) { @@ -104,8 +123,8 @@ namespace osu.Game.Overlays if (!Current.Disabled) { this.FadeTo(Current.IsDefault ? 0 : 1, fade_duration, Easing.OutQuint); - Background.FadeColour(IsHovered ? colours.Lime0 : colours.Lime1, fade_duration, Easing.OutQuint); - Content.TweenEdgeEffectTo(new EdgeEffectParameters + background.FadeColour(IsHovered ? colours.Lime0 : colours.Lime1, fade_duration, Easing.OutQuint); + circle.TweenEdgeEffectTo(new EdgeEffectParameters { Colour = (IsHovered ? colours.Lime1 : colours.Lime3).Opacity(0.4f), Radius = IsHovered ? 8 : 4, @@ -114,8 +133,8 @@ namespace osu.Game.Overlays } else { - Background.FadeColour(colours.Lime3, fade_duration, Easing.OutQuint); - Content.TweenEdgeEffectTo(new EdgeEffectParameters + background.FadeColour(colours.Lime3, fade_duration, Easing.OutQuint); + circle.TweenEdgeEffectTo(new EdgeEffectParameters { Colour = colours.Lime3.Opacity(0.1f), Radius = 2, From b03291330f2fb89dd482a53b62c23caeeb1b0757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Dec 2022 21:23:50 +0100 Subject: [PATCH 009/138] Add score processed callback to spectator client --- osu.Game/Online/Spectator/ISpectatorClient.cs | 7 +++++++ osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 1 + osu.Game/Online/Spectator/SpectatorClient.cs | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs index ccba280001..605ebc4ef0 100644 --- a/osu.Game/Online/Spectator/ISpectatorClient.cs +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -32,5 +32,12 @@ namespace osu.Game.Online.Spectator /// The user. /// The frame data. Task UserSentFrames(int userId, FrameDataBundle data); + + /// + /// Signals that a user's submitted score was fully processed. + /// + /// The ID of the user who achieved the score. + /// The ID of the score. + Task UserScoreProcessed(int userId, long scoreId); } } diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index 01b775549e..3118e05053 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -41,6 +41,7 @@ namespace osu.Game.Online.Spectator connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); + connection.On(nameof(ISpectatorClient.UserScoreProcessed), ((ISpectatorClient)this).UserScoreProcessed); }; IsConnected.BindTo(connector.IsConnected); diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index fce61c019b..b60cef2835 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -64,6 +64,11 @@ namespace osu.Game.Online.Spectator /// public virtual event Action? OnUserFinishedPlaying; + /// + /// Called whenever a user-submitted score has been fully processed. + /// + public virtual event Action? OnUserScoreProcessed; + /// /// A dictionary containing all users currently being watched, with the number of watching components for each user. /// @@ -160,6 +165,13 @@ namespace osu.Game.Online.Spectator return Task.CompletedTask; } + Task ISpectatorClient.UserScoreProcessed(int userId, long scoreId) + { + Schedule(() => OnUserScoreProcessed?.Invoke(userId, scoreId)); + + return Task.CompletedTask; + } + public void BeginPlaying(long? scoreToken, GameplayState state, Score score) { // This schedule is only here to match the one below in `EndPlaying`. From 19f66c806e7a7df51dee54d8ff935cdf0b96e5a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Dec 2022 16:31:53 +0800 Subject: [PATCH 010/138] Fix language dropdown in settings not updating after changing language in first run dialog Closes #21744. --- .../Overlays/Settings/Sections/General/LanguageSettings.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs index a4ec919658..982cbec376 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs @@ -44,10 +44,13 @@ namespace osu.Game.Overlays.Settings.Sections.General }, }; - localisationParameters.BindValueChanged(p - => languageSelection.Current.Value = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, p.NewValue), true); + frameworkLocale.BindValueChanged(_ => updateSelection()); + localisationParameters.BindValueChanged(_ => updateSelection(), true); languageSelection.Current.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToCultureCode()); } + + private void updateSelection() => + languageSelection.Current.Value = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, localisationParameters.Value); } } From 3ec31a5f51762fc1d9ed81256bc40a13597cf04e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Dec 2022 19:30:21 +0100 Subject: [PATCH 011/138] Fix language selector in first run dialog not updating after changing language in settings --- .../Overlays/FirstRunSetup/ScreenWelcome.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index f6133e3643..4af40e5ad6 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -87,18 +87,21 @@ namespace osu.Game.Overlays.FirstRunSetup }); frameworkLocale = frameworkConfig.GetBindable(FrameworkSetting.Locale); + frameworkLocale.BindValueChanged(_ => onLanguageChange()); localisationParameters = localisation.CurrentParameters.GetBoundCopy(); - localisationParameters.BindValueChanged(p => - { - var language = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, p.NewValue); + localisationParameters.BindValueChanged(_ => onLanguageChange(), true); + } - // Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded. - // Scheduling ensures the button animation plays smoothly after any blocking operation completes. - // Note that a delay is required (the alternative would be a double-schedule; delay feels better). - updateSelectedDelegate?.Cancel(); - updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(language), 50); - }, true); + private void onLanguageChange() + { + var language = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, localisationParameters.Value); + + // Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded. + // Scheduling ensures the button animation plays smoothly after any blocking operation completes. + // Note that a delay is required (the alternative would be a double-schedule; delay feels better). + updateSelectedDelegate?.Cancel(); + updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(language), 50); } private void updateSelectedStates(Language language) From 0a49c8c5d67b67c7148ba2ca9fe92c89bb070b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Dec 2022 20:03:46 +0100 Subject: [PATCH 012/138] Add missing unsubscriptions in multiple mania components --- osu.Game.Rulesets.Mania/UI/Column.cs | 3 +++ osu.Game.Rulesets.Mania/UI/Stage.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 6a31fb3fda..10460f52fd 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -134,6 +134,9 @@ namespace osu.Game.Rulesets.Mania.UI protected override void Dispose(bool isDisposing) { + // must happen before children are disposed in base call to prevent illegal accesses to the hit explosion pool. + NewResult -= OnNewResult; + base.Dispose(isDisposing); if (skin != null) diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index fc38a96a35..c1d3e85bf1 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -156,6 +156,9 @@ namespace osu.Game.Rulesets.Mania.UI protected override void Dispose(bool isDisposing) { + // must happen before children are disposed in base call to prevent illegal accesses to the judgement pool. + NewResult -= OnNewResult; + base.Dispose(isDisposing); if (currentSkin != null) From 6948035a3cc89d6cba9ad7263e73f89d1f94aacc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Dec 2022 21:59:46 +0100 Subject: [PATCH 013/138] Ensure score submission attempt completion before notifying spectator server when exiting play early When a `SubmittingPlayer` gameplay session ends with the successful completion of a beatmap, `PrepareScoreForResultsAsync()` ensures that the score submission request is sent to and responded to by osu-web before calling `ISpectatorClient.EndPlaying()`. While previously this was mostly an implementation detail, this becomes important when considering that more and more server-side flows (replay upload, notifying about score processing completion) hook into `EndPlaying()`, and assume that by the point that message arrives at osu-spectator-server, the score has already been submitted and has been assigned a score ID that corresponds to the score submission token. As it turns out, in the early-exit path (when the user exits the play midway through, retries, or just fails), the same ordering guarantees were not provided. The score's submission ran concurrently to the spectator client `EndPlaying()` call, therefore creating a network race. osu-server-spectator components that implciitly relied on the ordering provided by the happy path, could therefore fail to unmap the score submission token to a score ID. Note that as written, the osu-server-spectator replay upload flow is not really affected by this, as it self-corrects by essentially polling the database and trying to unmap the score submission token to a score ID for up to 30 seconds. However, this change would have the benefit of reducing the polls required in such cases to just one DB retrieval. --- osu.Game/Screens/Play/SubmittingPlayer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 1eec71f33a..5fa6508a31 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -13,6 +13,7 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Scoring; @@ -158,8 +159,11 @@ namespace osu.Game.Screens.Play if (LoadedBeatmapSuccessfully) { - submitScore(Score.DeepClone()); - spectatorClient.EndPlaying(GameplayState); + Task.Run(async () => + { + await submitScore(Score.DeepClone()).ConfigureAwait(false); + spectatorClient.EndPlaying(GameplayState); + }).FireAndForget(); } return exiting; From f5b3988dd2bd7ccc82437e191592d89650d34a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Dec 2022 08:01:52 +0100 Subject: [PATCH 014/138] Add data structure for delivering statistics updates --- osu.Game/Online/Solo/SoloStatisticsUpdate.cs | 42 ++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 osu.Game/Online/Solo/SoloStatisticsUpdate.cs diff --git a/osu.Game/Online/Solo/SoloStatisticsUpdate.cs b/osu.Game/Online/Solo/SoloStatisticsUpdate.cs new file mode 100644 index 0000000000..cb9dac97c7 --- /dev/null +++ b/osu.Game/Online/Solo/SoloStatisticsUpdate.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Online.Solo +{ + /// + /// Contains data about the change in a user's profile statistics after completing a score. + /// + public class SoloStatisticsUpdate + { + /// + /// The score set by the user that triggered the update. + /// + public ScoreInfo Score { get; } + + /// + /// The user's profile statistics prior to the score being set. + /// + public UserStatistics Before { get; } + + /// + /// The user's profile statistics after the score was set. + /// + public UserStatistics After { get; } + + /// + /// Creates a new . + /// + /// The score set by the user that triggered the update. + /// The user's profile statistics prior to the score being set. + /// The user's profile statistics after the score was set. + public SoloStatisticsUpdate(ScoreInfo score, UserStatistics before, UserStatistics after) + { + Score = score; + Before = before; + After = after; + } + } +} From 422fdd8ae5a3f8deae62b788fb41d84f8e8608e8 Mon Sep 17 00:00:00 2001 From: Flutterish Date: Thu, 22 Dec 2022 16:56:27 +0100 Subject: [PATCH 015/138] dont post notifications from custom log targets --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a0a45e18a8..af58a72ae8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1040,7 +1040,7 @@ namespace osu.Game Logger.NewEntry += entry => { - if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database) return; + if (entry.Level < LogLevel.Important || entry.Target is null or > LoggingTarget.Database) return; Debug.Assert(entry.Target != null); From 5df440e20eb8389e14e029879af28d1bab630a94 Mon Sep 17 00:00:00 2001 From: Flutterish Date: Thu, 22 Dec 2022 17:27:55 +0100 Subject: [PATCH 016/138] dont use `is..or` syntax --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index af58a72ae8..b5e1023ac6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1040,7 +1040,7 @@ namespace osu.Game Logger.NewEntry += entry => { - if (entry.Level < LogLevel.Important || entry.Target is null or > LoggingTarget.Database) return; + if (entry.Level < LogLevel.Important || entry.Target == null || entry.Target > LoggingTarget.Database) return; Debug.Assert(entry.Target != null); From 8be6350c019bddbb8064d73aa013ffbc71e58c09 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Dec 2022 20:07:53 +0300 Subject: [PATCH 017/138] Remove no longer necessary assert --- osu.Game/OsuGame.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b5e1023ac6..de9a009f44 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1040,9 +1040,7 @@ namespace osu.Game Logger.NewEntry += entry => { - if (entry.Level < LogLevel.Important || entry.Target == null || entry.Target > LoggingTarget.Database) return; - - Debug.Assert(entry.Target != null); + if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database || entry.Target == null) return; const int short_term_display_limit = 3; From ac872fac9e562b4e4cba19ecf5a8bc31ba269f4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Dec 2022 09:04:53 +0100 Subject: [PATCH 018/138] Implement solo statistics watcher --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 osu.Game/Online/Solo/SoloStatisticsWatcher.cs diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs new file mode 100644 index 0000000000..197ad410a9 --- /dev/null +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -0,0 +1,140 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Spectator; +using osu.Game.Scoring; +using osu.Game.Users; + +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 + { + [Resolved] + private SpectatorClient spectatorClient { get; set; } = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + private readonly Dictionary callbacks = new Dictionary(); + private readonly HashSet scoresWithoutCallback = new HashSet(); + + private readonly Dictionary latestStatistics = new Dictionary(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + api.LocalUser.BindValueChanged(user => onUserChanged(user.NewValue), true); + spectatorClient.OnUserScoreProcessed += userScoreProcessed; + } + + /// + /// 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. + public void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady) => Schedule(() => + { + if (!api.IsLoggedIn) + return; + + var callback = new StatisticsUpdateCallback(score, onUpdateReady); + + if (scoresWithoutCallback.Remove(score.OnlineID)) + { + requestStatisticsUpdate(api.LocalUser.Value.Id, callback); + return; + } + + callbacks[score.OnlineID] = callback; + }); + + private void onUserChanged(APIUser? localUser) => Schedule(() => + { + callbacks.Clear(); + scoresWithoutCallback.Clear(); + latestStatistics.Clear(); + + if (!api.IsLoggedIn) + return; + + Debug.Assert(localUser != null && localUser.OnlineID > 1); + + var userRequest = new GetUsersRequest(new[] { localUser.OnlineID }); + userRequest.Success += response => Schedule(() => + { + foreach (var rulesetStats in response.Users.Single().RulesetsStatistics) + latestStatistics.Add(rulesetStats.Key, rulesetStats.Value); + }); + api.Queue(userRequest); + }); + + private void userScoreProcessed(int userId, long scoreId) + { + if (userId != api.LocalUser.Value?.OnlineID) + return; + + if (!callbacks.TryGetValue(scoreId, out var callback)) + { + scoresWithoutCallback.Add(scoreId); + return; + } + + requestStatisticsUpdate(userId, callback); + callbacks.Remove(scoreId); + } + + private void requestStatisticsUpdate(int userId, StatisticsUpdateCallback callback) + { + var request = new GetUserRequest(userId, callback.Score.Ruleset); + request.Success += user => Schedule(() => dispatchStatisticsUpdate(callback, user.Statistics)); + api.Queue(request); + } + + private void dispatchStatisticsUpdate(StatisticsUpdateCallback callback, UserStatistics updatedStatistics) + { + string rulesetName = callback.Score.Ruleset.ShortName; + + if (!latestStatistics.TryGetValue(rulesetName, out var latestRulesetStatistics)) + return; + + var update = new SoloStatisticsUpdate(callback.Score, latestRulesetStatistics, updatedStatistics); + callback.OnUpdateReady.Invoke(update); + + latestStatistics[rulesetName] = updatedStatistics; + } + + protected override void Dispose(bool isDisposing) + { + if (spectatorClient.IsNotNull()) + spectatorClient.OnUserScoreProcessed -= userScoreProcessed; + + base.Dispose(isDisposing); + } + + private class StatisticsUpdateCallback + { + public ScoreInfo Score { get; } + public Action OnUpdateReady { get; } + + public StatisticsUpdateCallback(ScoreInfo score, Action onUpdateReady) + { + Score = score; + OnUpdateReady = onUpdateReady; + } + } + } +} From 722cf48614fb696a8f9f8ff01512da826edeac90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Dec 2022 10:14:37 +0100 Subject: [PATCH 019/138] Add test coverage for statistics watcher --- .../Online/TestSceneSoloStatisticsWatcher.cs | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs new file mode 100644 index 0000000000..0797113ca1 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -0,0 +1,241 @@ +// 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.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Models; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Solo; +using osu.Game.Online.Spectator; +using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + [HeadlessTest] + public partial class TestSceneSoloStatisticsWatcher : OsuTestScene + { + protected override bool UseOnlineAPI => false; + + private SoloStatisticsWatcher watcher = null!; + + [Resolved] + private SpectatorClient spectatorClient { get; set; } = null!; + + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + private Action? handleGetUsersRequest; + private Action? handleGetUserRequest; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("set up request handling", () => + { + handleGetUserRequest = null; + handleGetUsersRequest = null; + + dummyAPI.HandleRequest = request => + { + switch (request) + { + case GetUsersRequest getUsersRequest: + handleGetUsersRequest?.Invoke(getUsersRequest); + return true; + + case GetUserRequest getUserRequest: + handleGetUserRequest?.Invoke(getUserRequest); + return true; + + default: + return false; + } + }; + }); + + AddStep("create watcher", () => + { + Child = watcher = new SoloStatisticsWatcher(); + }); + } + + [Test] + public void TestStatisticsUpdateFiredAfterRegistrationAddedAndScoreProcessed() + { + AddStep("fetch initial stats", () => + { + handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1234)); + dummyAPI.LocalUser.Value = new APIUser { Id = 1234 }; + }); + + SoloStatisticsUpdate? update = null; + + AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + { + Ruleset = new OsuRuleset().RulesetInfo, + OnlineID = 5678 + }, + receivedUpdate => update = receivedUpdate)); + + AddStep("feign score processing", + () => handleGetUserRequest = + req => req.TriggerSuccess(createIncrementalUserResponse(1234, 5_000_000))); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1234, 5678)); + AddUntilStep("update received", () => update != null); + AddAssert("values before are correct", () => update?.Before.TotalScore, () => Is.EqualTo(4_000_000)); + AddAssert("values after are correct", () => update?.After.TotalScore, () => Is.EqualTo(5_000_000)); + } + + [Test] + public void TestStatisticsUpdateFiredAfterScoreProcessedAndRegistrationAdded() + { + AddStep("fetch initial stats", () => + { + handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1235)); + dummyAPI.LocalUser.Value = new APIUser { Id = 1235 }; + }); + + AddStep("feign score processing", + () => handleGetUserRequest = + req => req.TriggerSuccess(createIncrementalUserResponse(1235, 5_000_000))); + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1235, 5678)); + + SoloStatisticsUpdate? update = null; + + // note ordering - this test checks that even if the registration is late, it will receive data. + AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + { + Ruleset = new OsuRuleset().RulesetInfo, + OnlineID = 5678 + }, + receivedUpdate => update = receivedUpdate)); + AddUntilStep("update received", () => update != null); + AddAssert("values before are correct", () => update?.Before.TotalScore, () => Is.EqualTo(4_000_000)); + AddAssert("values after are correct", () => update?.After.TotalScore, () => Is.EqualTo(5_000_000)); + } + + [Test] + public void TestStatisticsUpdateNotFiredIfUserLoggedOut() + { + AddStep("fetch initial stats", () => + { + handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1236)); + dummyAPI.LocalUser.Value = new APIUser { Id = 1236 }; + }); + + SoloStatisticsUpdate? update = null; + + AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + { + Ruleset = new OsuRuleset().RulesetInfo, + OnlineID = 5678 + }, + receivedUpdate => update = receivedUpdate)); + + AddStep("feign score processing", + () => handleGetUserRequest = + req => req.TriggerSuccess(createIncrementalUserResponse(1236, 5_000_000))); + + AddStep("log out user", () => dummyAPI.Logout()); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1236, 5678)); + AddWaitStep("wait a bit", 5); + AddAssert("update not received", () => update == null); + } + + [Test] + public void TestStatisticsUpdateNotFiredIfAnotherUserLoggedIn() + { + AddStep("fetch initial stats", () => + { + handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1237)); + dummyAPI.LocalUser.Value = new APIUser { Id = 1237 }; + }); + + SoloStatisticsUpdate? update = null; + + AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + { + Ruleset = new OsuRuleset().RulesetInfo, + OnlineID = 5678 + }, + receivedUpdate => update = receivedUpdate)); + + AddStep("feign score processing", + () => handleGetUserRequest = + req => req.TriggerSuccess(createIncrementalUserResponse(1237, 5_000_000))); + + AddStep("log out user", () => dummyAPI.LocalUser.Value = new APIUser { Id = 5555 }); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1237, 5678)); + AddWaitStep("wait a bit", 5); + AddAssert("update not received", () => update == null); + } + + [Test] + public void TestStatisticsUpdateNotFiredIfScoreIdDoesNotMatch() + { + AddStep("fetch initial stats", () => + { + handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1238)); + dummyAPI.LocalUser.Value = new APIUser { Id = 1238 }; + }); + + SoloStatisticsUpdate? update = null; + + AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + { + Ruleset = new OsuRuleset().RulesetInfo, + OnlineID = 5678 + }, + receivedUpdate => update = receivedUpdate)); + + AddStep("feign score processing", + () => handleGetUserRequest = + req => req.TriggerSuccess(createIncrementalUserResponse(1238, 5_000_000))); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1238, 9012)); + AddWaitStep("wait a bit", 5); + AddAssert("update not received", () => update == null); + } + + private GetUsersResponse createInitialUserResponse(int userId) => new GetUsersResponse + { + Users = new List + { + new APIUser + { + Id = userId, + RulesetsStatistics = new Dictionary + { + ["osu"] = new UserStatistics { TotalScore = 4_000_000 }, + ["taiko"] = new UserStatistics { TotalScore = 3_000_000 }, + ["fruits"] = new UserStatistics { TotalScore = 2_000_000 }, + ["mania"] = new UserStatistics { TotalScore = 1_000_000 } + } + } + } + }; + + private APIUser createIncrementalUserResponse(int userId, long totalScore) => new APIUser + { + Id = userId, + Statistics = new UserStatistics + { + TotalScore = totalScore + } + }; + } +} From 48dc2332fd5bc60d977897e3a4d6477dc8b0deec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Dec 2022 11:10:33 +0100 Subject: [PATCH 020/138] Refactor test to be easier to work with --- .../Online/TestSceneSoloStatisticsWatcher.cs | 241 +++++++++--------- .../Online/API/Requests/GetUsersRequest.cs | 6 +- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 2 +- 3 files changed, 130 insertions(+), 119 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index 0797113ca1..008d54be63 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; @@ -12,6 +13,7 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Solo; using osu.Game.Online.Spectator; +using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Users; @@ -33,9 +35,12 @@ namespace osu.Game.Tests.Visual.Online private Action? handleGetUsersRequest; private Action? handleGetUserRequest; + private readonly Dictionary<(int userId, string rulesetName), UserStatistics> serverSideStatistics = new Dictionary<(int userId, string rulesetName), UserStatistics>(); + [SetUpSteps] public void SetUpSteps() { + AddStep("clear server-side stats", () => serverSideStatistics.Clear()); AddStep("set up request handling", () => { handleGetUserRequest = null; @@ -46,11 +51,52 @@ namespace osu.Game.Tests.Visual.Online switch (request) { case GetUsersRequest getUsersRequest: - handleGetUsersRequest?.Invoke(getUsersRequest); + if (handleGetUsersRequest != null) + { + handleGetUsersRequest?.Invoke(getUsersRequest); + } + else + { + int userId = getUsersRequest.UserIds.Single(); + var response = new GetUsersResponse + { + Users = new List + { + new APIUser + { + Id = userId, + RulesetsStatistics = new Dictionary + { + ["osu"] = tryGetStatistics(userId, "osu"), + ["taiko"] = tryGetStatistics(userId, "taiko"), + ["fruits"] = tryGetStatistics(userId, "fruits"), + ["mania"] = tryGetStatistics(userId, "mania"), + } + } + } + }; + getUsersRequest.TriggerSuccess(response); + } + return true; case GetUserRequest getUserRequest: - handleGetUserRequest?.Invoke(getUserRequest); + if (handleGetUserRequest != null) + { + handleGetUserRequest.Invoke(getUserRequest); + } + else + { + int userId = int.Parse(getUserRequest.Lookup); + string rulesetName = getUserRequest.Ruleset.ShortName; + var response = new APIUser + { + Id = userId, + Statistics = tryGetStatistics(userId, rulesetName) + }; + getUserRequest.TriggerSuccess(response); + } + return true; default: @@ -65,120 +111,90 @@ namespace osu.Game.Tests.Visual.Online }); } + private UserStatistics tryGetStatistics(int userId, string rulesetName) + => serverSideStatistics.TryGetValue((userId, rulesetName), out var stats) ? stats : new UserStatistics(); + [Test] public void TestStatisticsUpdateFiredAfterRegistrationAddedAndScoreProcessed() { - AddStep("fetch initial stats", () => - { - handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1234)); - dummyAPI.LocalUser.Value = new APIUser { Id = 1234 }; - }); + int userId = getUserId(); + long scoreId = getScoreId(); + setUpUser(userId); + + var ruleset = new OsuRuleset().RulesetInfo; SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); - AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( - new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) - { - Ruleset = new OsuRuleset().RulesetInfo, - OnlineID = 5678 - }, - receivedUpdate => update = receivedUpdate)); + feignScoreProcessing(userId, ruleset, 5_000_000); - AddStep("feign score processing", - () => handleGetUserRequest = - req => req.TriggerSuccess(createIncrementalUserResponse(1234, 5_000_000))); - - AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1234, 5678)); + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); AddUntilStep("update received", () => update != null); - AddAssert("values before are correct", () => update?.Before.TotalScore, () => Is.EqualTo(4_000_000)); - AddAssert("values after are correct", () => update?.After.TotalScore, () => Is.EqualTo(5_000_000)); + AddAssert("values before are correct", () => update!.Before.TotalScore, () => Is.EqualTo(4_000_000)); + AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(5_000_000)); } [Test] public void TestStatisticsUpdateFiredAfterScoreProcessedAndRegistrationAdded() { - AddStep("fetch initial stats", () => - { - handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1235)); - dummyAPI.LocalUser.Value = new APIUser { Id = 1235 }; - }); + int userId = getUserId(); + setUpUser(userId); - AddStep("feign score processing", - () => handleGetUserRequest = - req => req.TriggerSuccess(createIncrementalUserResponse(1235, 5_000_000))); - AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1235, 5678)); + long scoreId = getScoreId(); + var ruleset = new OsuRuleset().RulesetInfo; + + // note ordering - in this test processing completes *before* the registration is added. + feignScoreProcessing(userId, ruleset, 5_000_000); SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); - // note ordering - this test checks that even if the registration is late, it will receive data. - AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( - new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) - { - Ruleset = new OsuRuleset().RulesetInfo, - OnlineID = 5678 - }, - receivedUpdate => update = receivedUpdate)); + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); AddUntilStep("update received", () => update != null); - AddAssert("values before are correct", () => update?.Before.TotalScore, () => Is.EqualTo(4_000_000)); - AddAssert("values after are correct", () => update?.After.TotalScore, () => Is.EqualTo(5_000_000)); + AddAssert("values before are correct", () => update!.Before.TotalScore, () => Is.EqualTo(4_000_000)); + AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(5_000_000)); } [Test] public void TestStatisticsUpdateNotFiredIfUserLoggedOut() { - AddStep("fetch initial stats", () => - { - handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1236)); - dummyAPI.LocalUser.Value = new APIUser { Id = 1236 }; - }); + int userId = getUserId(); + setUpUser(userId); + + long scoreId = getScoreId(); + var ruleset = new OsuRuleset().RulesetInfo; SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); - AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( - new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) - { - Ruleset = new OsuRuleset().RulesetInfo, - OnlineID = 5678 - }, - receivedUpdate => update = receivedUpdate)); - - AddStep("feign score processing", - () => handleGetUserRequest = - req => req.TriggerSuccess(createIncrementalUserResponse(1236, 5_000_000))); + feignScoreProcessing(userId, ruleset, 5_000_000); AddStep("log out user", () => dummyAPI.Logout()); - AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1236, 5678)); + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); AddWaitStep("wait a bit", 5); AddAssert("update not received", () => update == null); + + AddStep("log in user", () => dummyAPI.Login("user", "password")); } [Test] public void TestStatisticsUpdateNotFiredIfAnotherUserLoggedIn() { - AddStep("fetch initial stats", () => - { - handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1237)); - dummyAPI.LocalUser.Value = new APIUser { Id = 1237 }; - }); + int userId = getUserId(); + setUpUser(userId); + + long scoreId = getScoreId(); + var ruleset = new OsuRuleset().RulesetInfo; SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); - AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( - new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) - { - Ruleset = new OsuRuleset().RulesetInfo, - OnlineID = 5678 - }, - receivedUpdate => update = receivedUpdate)); + feignScoreProcessing(userId, ruleset, 5_000_000); - AddStep("feign score processing", - () => handleGetUserRequest = - req => req.TriggerSuccess(createIncrementalUserResponse(1237, 5_000_000))); + AddStep("change user", () => dummyAPI.LocalUser.Value = new APIUser { Id = getUserId() }); - AddStep("log out user", () => dummyAPI.LocalUser.Value = new APIUser { Id = 5555 }); - - AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1237, 5678)); + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); AddWaitStep("wait a bit", 5); AddAssert("update not received", () => update == null); } @@ -186,56 +202,51 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestStatisticsUpdateNotFiredIfScoreIdDoesNotMatch() { - AddStep("fetch initial stats", () => - { - handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1238)); - dummyAPI.LocalUser.Value = new APIUser { Id = 1238 }; - }); + int userId = getUserId(); + setUpUser(userId); + + long scoreId = getScoreId(); + var ruleset = new OsuRuleset().RulesetInfo; SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); - AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( - new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) - { - Ruleset = new OsuRuleset().RulesetInfo, - OnlineID = 5678 - }, - receivedUpdate => update = receivedUpdate)); + feignScoreProcessing(userId, ruleset, 5_000_000); - AddStep("feign score processing", - () => handleGetUserRequest = - req => req.TriggerSuccess(createIncrementalUserResponse(1238, 5_000_000))); - - AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1238, 9012)); + AddStep("signal another score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, getScoreId())); AddWaitStep("wait a bit", 5); AddAssert("update not received", () => update == null); } - private GetUsersResponse createInitialUserResponse(int userId) => new GetUsersResponse - { - Users = new List - { - new APIUser - { - Id = userId, - RulesetsStatistics = new Dictionary - { - ["osu"] = new UserStatistics { TotalScore = 4_000_000 }, - ["taiko"] = new UserStatistics { TotalScore = 3_000_000 }, - ["fruits"] = new UserStatistics { TotalScore = 2_000_000 }, - ["mania"] = new UserStatistics { TotalScore = 1_000_000 } - } - } - } - }; + private int nextUserId = 2000; + private long nextScoreId = 50000; - private APIUser createIncrementalUserResponse(int userId, long totalScore) => new APIUser + private int getUserId() => ++nextUserId; + private long getScoreId() => ++nextScoreId; + + private void setUpUser(int userId) { - Id = userId, - Statistics = new UserStatistics + AddStep("fetch initial stats", () => { - TotalScore = totalScore - } - }; + serverSideStatistics[(userId, "osu")] = new UserStatistics { TotalScore = 4_000_000 }; + serverSideStatistics[(userId, "taiko")] = new UserStatistics { TotalScore = 3_000_000 }; + serverSideStatistics[(userId, "fruits")] = new UserStatistics { TotalScore = 2_000_000 }; + serverSideStatistics[(userId, "mania")] = new UserStatistics { TotalScore = 1_000_000 }; + + dummyAPI.LocalUser.Value = new APIUser { Id = userId }; + }); + } + + private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action onUpdateReady) => + AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + { + Ruleset = rulesetInfo, + OnlineID = scoreId + }, + onUpdateReady)); + + private void feignScoreProcessing(int userId, RulesetInfo rulesetInfo, long newTotalScore) + => AddStep("feign score processing", () => serverSideStatistics[(userId, rulesetInfo.ShortName)] = new UserStatistics { TotalScore = newTotalScore }); } } diff --git a/osu.Game/Online/API/Requests/GetUsersRequest.cs b/osu.Game/Online/API/Requests/GetUsersRequest.cs index bbaf241384..b57bb215aa 100644 --- a/osu.Game/Online/API/Requests/GetUsersRequest.cs +++ b/osu.Game/Online/API/Requests/GetUsersRequest.cs @@ -9,7 +9,7 @@ namespace osu.Game.Online.API.Requests { public class GetUsersRequest : APIRequest { - private readonly int[] userIds; + public readonly int[] UserIds; private const int max_ids_per_request = 50; @@ -18,9 +18,9 @@ namespace osu.Game.Online.API.Requests if (userIds.Length > max_ids_per_request) throw new ArgumentException($"{nameof(GetUsersRequest)} calls only support up to {max_ids_per_request} IDs at once"); - this.userIds = userIds; + UserIds = userIds; } - protected override string Target => "users/?ids[]=" + string.Join("&ids[]=", userIds); + protected override string Target => "users/?ids[]=" + string.Join("&ids[]=", UserIds); } } diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 197ad410a9..1befbe2af0 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -71,7 +71,7 @@ namespace osu.Game.Online.Solo if (!api.IsLoggedIn) return; - Debug.Assert(localUser != null && localUser.OnlineID > 1); + Debug.Assert(localUser != null); var userRequest = new GetUsersRequest(new[] { localUser.OnlineID }); userRequest.Success += response => Schedule(() => From fa2d50fe3164612e17bedb6d1892294cc9af0a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Dec 2022 19:29:51 +0100 Subject: [PATCH 021/138] Limit tracking unhandled scores to just the last one --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 1befbe2af0..48f39504a3 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -29,7 +29,7 @@ namespace osu.Game.Online.Solo private IAPIProvider api { get; set; } = null!; private readonly Dictionary callbacks = new Dictionary(); - private readonly HashSet scoresWithoutCallback = new HashSet(); + private long? lastProcessedScoreId; private readonly Dictionary latestStatistics = new Dictionary(); @@ -53,7 +53,7 @@ namespace osu.Game.Online.Solo var callback = new StatisticsUpdateCallback(score, onUpdateReady); - if (scoresWithoutCallback.Remove(score.OnlineID)) + if (lastProcessedScoreId == score.OnlineID) { requestStatisticsUpdate(api.LocalUser.Value.Id, callback); return; @@ -65,7 +65,7 @@ namespace osu.Game.Online.Solo private void onUserChanged(APIUser? localUser) => Schedule(() => { callbacks.Clear(); - scoresWithoutCallback.Clear(); + lastProcessedScoreId = null; latestStatistics.Clear(); if (!api.IsLoggedIn) @@ -87,11 +87,10 @@ namespace osu.Game.Online.Solo if (userId != api.LocalUser.Value?.OnlineID) return; + lastProcessedScoreId = scoreId; + if (!callbacks.TryGetValue(scoreId, out var callback)) - { - scoresWithoutCallback.Add(scoreId); return; - } requestStatisticsUpdate(userId, callback); callbacks.Remove(scoreId); From 27afeb9e301d0392be3a22155643389468c04448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Dec 2022 19:46:41 +0100 Subject: [PATCH 022/138] Add test coverage of merging ignored score updates --- .../Online/TestSceneSoloStatisticsWatcher.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index 008d54be63..b1badc6282 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -218,6 +218,34 @@ namespace osu.Game.Tests.Visual.Online AddAssert("update not received", () => update == null); } + // the behaviour exercised in this test may not be final, it is mostly assumed for simplicity. + // in the long run we may want each score's update to be entirely isolated from others, rather than have prior unobserved updates merge into the latest. + [Test] + public void TestIgnoredScoreUpdateIsMergedIntoNextOne() + { + int userId = getUserId(); + setUpUser(userId); + + long firstScoreId = getScoreId(); + var ruleset = new OsuRuleset().RulesetInfo; + + feignScoreProcessing(userId, ruleset, 5_000_000); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, firstScoreId)); + + long secondScoreId = getScoreId(); + + feignScoreProcessing(userId, ruleset, 6_000_000); + + SoloStatisticsUpdate? update = null; + registerForUpdates(secondScoreId, ruleset, receivedUpdate => update = receivedUpdate); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, secondScoreId)); + AddUntilStep("update received", () => update != null); + AddAssert("values before are correct", () => update!.Before.TotalScore, () => Is.EqualTo(4_000_000)); + AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(6_000_000)); + } + private int nextUserId = 2000; private long nextScoreId = 50000; From 08d2fbeb8e99cc2ed2b658f1ce1d30f859108c2b Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Thu, 22 Dec 2022 21:27:59 +0100 Subject: [PATCH 023/138] Use new ArgumentNullException.ThrowIfNull throw-helper API --- .../Beatmaps/Patterns/Legacy/PatternGenerator.cs | 4 ++-- .../Beatmaps/Patterns/PatternGenerator.cs | 6 +++--- osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs | 3 +-- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 3 +-- osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs | 4 ++-- osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 2 +- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 3 +-- osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs | 3 +-- osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs | 3 +-- osu.Game/Beatmaps/Formats/Decoder.cs | 3 +-- osu.Game/Graphics/Containers/LogoTrackingContainer.cs | 3 +-- osu.Game/Graphics/UserInterface/Nub.cs | 3 +-- osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs | 3 +-- osu.Game/Online/Chat/ChannelManager.cs | 6 ++---- osu.Game/Overlays/ChangelogOverlay.cs | 6 +++--- osu.Game/Overlays/OnScreenDisplay.cs | 4 ++-- osu.Game/Overlays/Volume/MuteButton.cs | 3 +-- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 3 +-- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 3 +-- osu.Game/Rulesets/UI/JudgementContainer.cs | 2 +- osu.Game/Scoring/ScoreImporter.cs | 4 ++-- osu.Game/Screens/Menu/LogoVisualisation.cs | 2 +- osu.Game/Screens/Play/HUD/ModDisplay.cs | 3 +-- osu.Game/Screens/Play/HUD/ModFlowDisplay.cs | 3 +-- osu.Game/Screens/Play/KeyCounterDisplay.cs | 2 +- osu.Game/Users/Drawables/DrawableFlag.cs | 3 +-- osu.Game/Users/UserPanel.cs | 3 +-- 27 files changed, 36 insertions(+), 54 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index 308238d87a..77f93b4ef9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy protected PatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) : base(hitObject, beatmap, previousPattern) { - if (random == null) throw new ArgumentNullException(nameof(random)); - if (originalBeatmap == null) throw new ArgumentNullException(nameof(originalBeatmap)); + ArgumentNullException.ThrowIfNull(random); + ArgumentNullException.ThrowIfNull(originalBeatmap); Random = random; OriginalBeatmap = originalBeatmap; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs index b2e89c3410..931673f337 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs @@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns protected PatternGenerator(HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern) { - if (hitObject == null) throw new ArgumentNullException(nameof(hitObject)); - if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); - if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern)); + ArgumentNullException.ThrowIfNull(hitObject); + ArgumentNullException.ThrowIfNull(beatmap); + ArgumentNullException.ThrowIfNull(previousPattern); HitObject = hitObject; Beatmap = beatmap; diff --git a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs index 1a67117c03..4d93826240 100644 --- a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs +++ b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs @@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils public static void Sort(T[] keys, IComparer comparer) { - if (keys == null) - throw new ArgumentNullException(nameof(keys)); + ArgumentNullException.ThrowIfNull(keys); if (keys.Length == 0) return; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 01e9926ad7..e3ebadc836 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Mania.UI public ManiaPlayfield(List stageDefinitions) { - if (stageDefinitions == null) - throw new ArgumentNullException(nameof(stageDefinitions)); + ArgumentNullException.ThrowIfNull(stageDefinitions); if (stageDefinitions.Count <= 0) throw new ArgumentException("Can't have zero or fewer stages."); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 2f62968029..74e16f7e0b 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -84,8 +84,8 @@ namespace osu.Game.Rulesets.Osu.Replays { public int Compare(ReplayFrame? f1, ReplayFrame? f2) { - if (f1 == null) throw new ArgumentNullException(nameof(f1)); - if (f2 == null) throw new ArgumentNullException(nameof(f2)); + ArgumentNullException.ThrowIfNull(f1); + ArgumentNullException.ThrowIfNull(f2); return f1.Time.CompareTo(f2.Time); } diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 52769321a9..1157b50377 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tournament.Components public TournamentBeatmapPanel(TournamentBeatmap beatmap, string mod = null) { - if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); + ArgumentNullException.ThrowIfNull(beatmap); Beatmap = beatmap; this.mod = mod; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 55119c800a..29b7191ecf 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -211,8 +211,7 @@ namespace osu.Game.Beatmaps.ControlPoints public static T BinarySearch(IReadOnlyList list, double time) where T : class, IControlPoint { - if (list == null) - throw new ArgumentNullException(nameof(list)); + ArgumentNullException.ThrowIfNull(list); if (list.Count == 0) return null; diff --git a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs index d31a7ae2fe..767504fcb1 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs @@ -15,8 +15,7 @@ namespace osu.Game.Beatmaps.Drawables public BeatmapBackgroundSprite(IWorkingBeatmap working) { - if (working == null) - throw new ArgumentNullException(nameof(working)); + ArgumentNullException.ThrowIfNull(working); this.working = working; } diff --git a/osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs index e4ffc1d553..fc7c14e734 100644 --- a/osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs @@ -18,8 +18,7 @@ namespace osu.Game.Beatmaps.Drawables public OnlineBeatmapSetCover(IBeatmapSetOnlineInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover) { - if (set == null) - throw new ArgumentNullException(nameof(set)); + ArgumentNullException.ThrowIfNull(set); this.set = set; this.type = type; diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index ca1bcc97fd..4f0f11d053 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -57,8 +57,7 @@ namespace osu.Game.Beatmaps.Formats public static Decoder GetDecoder(LineBufferedReader stream) where T : new() { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); + ArgumentNullException.ThrowIfNull(stream); if (!decoders.TryGetValue(typeof(T), out var typedDecoders)) throw new IOException(@"Unknown decoder type"); diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index 735b8b4e7d..984d60d35e 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -36,8 +36,7 @@ namespace osu.Game.Graphics.Containers /// The easing type of the initial transform. public void StartTracking(OsuLogo logo, double duration = 0, Easing easing = Easing.None) { - if (logo == null) - throw new ArgumentNullException(nameof(logo)); + ArgumentNullException.ThrowIfNull(logo); if (logo.IsTracking && Logo == null) throw new InvalidOperationException($"Cannot track an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s"); diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 4f56872f42..7921dcf593 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -114,8 +114,7 @@ namespace osu.Game.Graphics.UserInterface get => current; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); current.UnbindBindings(); current.BindTo(value); diff --git a/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs b/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs index 8e6c3e5f3d..d47f936eb3 100644 --- a/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs +++ b/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs @@ -23,8 +23,7 @@ namespace osu.Game.IO.FileAbstraction public void CloseStream(Stream stream) { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); + ArgumentNullException.ThrowIfNull(stream); stream.Close(); } diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index a4661dcbd7..5d55374373 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -118,8 +118,7 @@ namespace osu.Game.Online.Chat /// public void OpenChannel(string name) { - if (name == null) - throw new ArgumentNullException(nameof(name)); + ArgumentNullException.ThrowIfNull(name); CurrentChannel.Value = AvailableChannels.FirstOrDefault(c => c.Name == name) ?? throw new ChannelNotFoundException(name); } @@ -130,8 +129,7 @@ namespace osu.Game.Online.Chat /// The user the private channel is opened with. public void OpenPrivateChannel(APIUser user) { - if (user == null) - throw new ArgumentNullException(nameof(user)); + ArgumentNullException.ThrowIfNull(user); if (user.Id == api.LocalUser.Value.Id) return; diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 90863a90a2..671d649dcf 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Overlays /// are specified, the header will instantly display them. public void ShowBuild([NotNull] APIChangelogBuild build) { - if (build == null) throw new ArgumentNullException(nameof(build)); + ArgumentNullException.ThrowIfNull(build); Current.Value = build; Show(); @@ -78,8 +78,8 @@ namespace osu.Game.Overlays public void ShowBuild([NotNull] string updateStream, [NotNull] string version) { - if (updateStream == null) throw new ArgumentNullException(nameof(updateStream)); - if (version == null) throw new ArgumentNullException(nameof(version)); + ArgumentNullException.ThrowIfNull(updateStream); + ArgumentNullException.ThrowIfNull(version); performAfterFetch(() => { diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index d60077cfa9..4f2dba7b2c 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -58,7 +58,7 @@ namespace osu.Game.Overlays /// If is already being tracked from the same . public void BeginTracking(object source, ITrackableConfigManager configManager) { - if (configManager == null) throw new ArgumentNullException(nameof(configManager)); + ArgumentNullException.ThrowIfNull(configManager); if (trackedConfigManagers.ContainsKey((source, configManager))) throw new InvalidOperationException($"{nameof(configManager)} is already registered."); @@ -82,7 +82,7 @@ namespace osu.Game.Overlays /// If is not being tracked from the same . public void StopTracking(object source, ITrackableConfigManager configManager) { - if (configManager == null) throw new ArgumentNullException(nameof(configManager)); + ArgumentNullException.ThrowIfNull(configManager); if (!trackedConfigManagers.TryGetValue((source, configManager), out var existing)) return; diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index 3bea1c840e..9cc346a38b 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -28,8 +28,7 @@ namespace osu.Game.Overlays.Volume get => current; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); current.UnbindBindings(); current.BindTo(value); diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 9d9c10b3ea..38ced4c9e7 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -126,8 +126,7 @@ namespace osu.Game.Rulesets.Mods get => this; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); if (currentBound != null) UnbindFrom(currentBound); BindTo(currentBound = value); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 096132d024..be5a7f71e7 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -208,8 +208,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public void Apply([NotNull] HitObject hitObject) { - if (hitObject == null) - throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); + ArgumentNullException.ThrowIfNull(hitObject); Apply(new SyntheticHitObjectEntry(hitObject)); } diff --git a/osu.Game/Rulesets/UI/JudgementContainer.cs b/osu.Game/Rulesets/UI/JudgementContainer.cs index 8381e6d6b5..7181e80206 100644 --- a/osu.Game/Rulesets/UI/JudgementContainer.cs +++ b/osu.Game/Rulesets/UI/JudgementContainer.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.UI { public override void Add(T judgement) { - if (judgement == null) throw new ArgumentNullException(nameof(judgement)); + ArgumentNullException.ThrowIfNull(judgement); // remove any existing judgements for the judged object. // this can be the case when rewinding. diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 797d80b7fa..a3d7fe5de0 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -71,8 +71,8 @@ namespace osu.Game.Scoring // These properties are known to be non-null, but these final checks ensure a null hasn't come from somewhere (or the refetch has failed). // Under no circumstance do we want these to be written to realm as null. - if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo)); - if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset)); + ArgumentNullException.ThrowIfNull(model.BeatmapInfo); + ArgumentNullException.ThrowIfNull(model.Ruleset); PopulateMaximumStatistics(model); diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index c67850bdf6..5000a97b3d 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Menu private void addAmplitudesFromSource(IHasAmplitudes source) { - if (source == null) throw new ArgumentNullException(nameof(source)); + ArgumentNullException.ThrowIfNull(source); var amplitudes = source.CurrentAmplitudes.FrequencyAmplitudes.Span; diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 3b50a22e3c..8b2b8f9464 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -33,8 +33,7 @@ namespace osu.Game.Screens.Play.HUD get => current.Current; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); current.Current = value; } diff --git a/osu.Game/Screens/Play/HUD/ModFlowDisplay.cs b/osu.Game/Screens/Play/HUD/ModFlowDisplay.cs index 23030e640b..38027c64ac 100644 --- a/osu.Game/Screens/Play/HUD/ModFlowDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModFlowDisplay.cs @@ -30,8 +30,7 @@ namespace osu.Game.Screens.Play.HUD get => current.Current; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); current.Current = value; } diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index d9ad3cfaf7..bb50d4a539 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Play public override void Add(KeyCounter key) { - if (key == null) throw new ArgumentNullException(nameof(key)); + ArgumentNullException.ThrowIfNull(key); base.Add(key); key.IsCounting = IsCounting; diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 0d209f47e8..929a29251d 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -27,8 +27,7 @@ namespace osu.Game.Users.Drawables [BackgroundDependencyLoader] private void load(TextureStore ts) { - if (ts == null) - throw new ArgumentNullException(nameof(ts)); + ArgumentNullException.ThrowIfNull(ts); string textureName = countryCode == CountryCode.Unknown ? "__" : countryCode.ToString(); Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__"); diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index e7af127a30..2f7232d5ea 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -36,8 +36,7 @@ namespace osu.Game.Users protected UserPanel(APIUser user) : base(HoverSampleSet.Button) { - if (user == null) - throw new ArgumentNullException(nameof(user)); + ArgumentNullException.ThrowIfNull(user); User = user; } From a6650136269a8d8841fabea30bf9310fc07e639f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Dec 2022 00:56:37 +0300 Subject: [PATCH 024/138] Add failing test case --- .../TestSceneZoomableScrollContainer.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs index 6bc2922253..a141e4d431 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing { public partial class TestSceneZoomableScrollContainer : OsuManualInputManagerTestScene { - private ZoomableScrollContainer scrollContainer; + private TestZoomableScrollContainer scrollContainer; private Drawable innerBox; [SetUpSteps] @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Editing RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(30) }, - scrollContainer = new ZoomableScrollContainer(1, 60, 1) + scrollContainer = new TestZoomableScrollContainer(1, 60, 1) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -93,6 +93,14 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth); } + [Test] + public void TestWidthUpdatesOnSecondZoomSetup() + { + AddAssert("Inner container width = 1x", () => innerBox.DrawWidth == scrollContainer.DrawWidth); + AddStep("reload zoom", () => scrollContainer.SetupZoom(10, 10, 60)); + AddAssert("Inner container width = 10x", () => innerBox.DrawWidth == scrollContainer.DrawWidth * 10); + } + [Test] public void TestZoom0() { @@ -190,5 +198,15 @@ namespace osu.Game.Tests.Visual.Editing private Quad scrollQuad => scrollContainer.ScreenSpaceDrawQuad; private Quad boxQuad => innerBox.ScreenSpaceDrawQuad; + + private partial class TestZoomableScrollContainer : ZoomableScrollContainer + { + public TestZoomableScrollContainer(int minimum, float maximum, float initial) + : base(minimum, maximum, initial) + { + } + + public new void SetupZoom(float initial, float minimum, float maximum) => base.SetupZoom(initial, minimum, maximum); + } } } From 0cb9b7983498cbc313a070174868a487a254e247 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Dec 2022 00:56:02 +0300 Subject: [PATCH 025/138] Fix `ZoomableScrollContainer` potentially not updating content width on setup --- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 28f7731354..951f4129d4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -99,9 +99,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline minZoom = minimum; maxZoom = maximum; - CurrentZoom = zoomTarget = initial; - isZoomSetUp = true; + CurrentZoom = zoomTarget = initial; + zoomedContentWidthCache.Invalidate(); + + isZoomSetUp = true; zoomedContent.Show(); } From 5eccafe19046c331468c6c6e2c9b47c84ead38a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Dec 2022 16:45:40 +0800 Subject: [PATCH 026/138] Fix wiki overlay showing error message when load is cancelled --- osu.Game/Overlays/WikiOverlay.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index a06c180948..9ccd0af2b6 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -118,7 +118,11 @@ namespace osu.Game.Overlays Loading.Show(); request.Success += response => Schedule(() => onSuccess(response)); - request.Failure += _ => Schedule(onFail); + request.Failure += ex => + { + if (ex is not OperationCanceledException) + Schedule(onFail); + }; api.PerformAsync(request); } From a677c8be0651e3440f0f21a0546160100f9be035 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Dec 2022 21:17:42 +0800 Subject: [PATCH 027/138] Change path on error --- osu.Game/Overlays/WikiOverlay.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 9ccd0af2b6..a9a131e481 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -21,6 +21,8 @@ namespace osu.Game.Overlays { private const string index_path = @"main_page"; + public string CurrentPath => path.Value; + private readonly Bindable path = new Bindable(index_path); private readonly Bindable wikiData = new Bindable(); @@ -105,6 +107,9 @@ namespace osu.Game.Overlays if (e.NewValue == wikiData.Value?.Path) return; + if (e.NewValue == "error") + return; + cancellationToken?.Cancel(); request?.Cancel(); @@ -152,6 +157,7 @@ namespace osu.Game.Overlays private void onFail() { + path.Value = "error"; LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/", $"Something went wrong when trying to fetch page \"{path.Value}\".\n\n[Return to the main page](Main_Page).")); } From 4a69cb4aae964ea8bd61178337e2b203a0e5cdc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Dec 2022 21:19:04 +0800 Subject: [PATCH 028/138] Add test coverage of wiki cancellation not causing error --- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 620fd710e3..4ad03c052f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Linq; using System.Net; using NUnit.Framework; using osu.Game.Online.API; @@ -29,6 +30,15 @@ namespace osu.Game.Tests.Visual.Online AddStep("Show main page", () => wiki.Show()); } + [Test] + public void TestCancellationDoesntShowError() + { + AddStep("Show main page", () => wiki.Show()); + AddStep("Show another page", () => wiki.ShowPage("Article_styling_criteria/Formatting")); + + AddUntilStep("Current path is not error", () => wiki.CurrentPath != "error"); + } + [Test] public void TestArticlePage() { From 137a32ade66cf6bc2b3e0a7cffcfbe1049a072c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Dec 2022 16:39:35 +0100 Subject: [PATCH 029/138] Remove unused using directive --- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 4ad03c052f..806d231cb4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Linq; using System.Net; using NUnit.Framework; using osu.Game.Online.API; From 3dfbb47b010aa6351951310dc9b008ce3cd2b6cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Dec 2022 16:42:24 +0100 Subject: [PATCH 030/138] Add test coverage for wrong error message --- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 806d231cb4..b0e4303ca4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -4,8 +4,11 @@ #nullable disable using System; +using System.Linq; using System.Net; using NUnit.Framework; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -65,7 +68,9 @@ namespace osu.Game.Tests.Visual.Online public void TestErrorPage() { setUpWikiResponse(responseArticlePage); - AddStep("Show Error Page", () => wiki.ShowPage("Error")); + AddStep("Show nonexistent page", () => wiki.ShowPage("This_page_will_error_out")); + AddUntilStep("Wait for error page", () => wiki.CurrentPath == "error"); + AddUntilStep("Error message correct", () => wiki.ChildrenOfType().Any(text => text.Text == "\"This_page_will_error_out\".")); } private void setUpWikiResponse(APIWikiPage r, string redirectionPath = null) From 9a2cc043611ab6e78feef7c18958747baa795061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Dec 2022 16:42:46 +0100 Subject: [PATCH 031/138] Fix wrong path being used in fail handler --- osu.Game/Overlays/WikiOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index a9a131e481..88dc2cd7a4 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays request.Failure += ex => { if (ex is not OperationCanceledException) - Schedule(onFail); + Schedule(onFail, request.Path); }; api.PerformAsync(request); @@ -155,11 +155,11 @@ namespace osu.Game.Overlays } } - private void onFail() + private void onFail(string originalPath) { path.Value = "error"; LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/", - $"Something went wrong when trying to fetch page \"{path.Value}\".\n\n[Return to the main page](Main_Page).")); + $"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page](Main_Page).")); } private void showParentPage() From 727ac00f6d5b744c6e83be66b7a6a98c7d6a984c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 03:22:04 +0800 Subject: [PATCH 032/138] Combine base class for `JudgementPiece` --- .../Skinning/Argon/ArgonJudgementPiece.cs | 11 ++----- .../Skinning/Argon/ArgonJudgementPiece.cs | 11 ++----- .../Skinning/Argon/ArgonJudgementPiece.cs | 11 ++----- .../Skinning/Argon/ArgonJudgementPiece.cs | 11 ++----- .../Judgements/DefaultJudgementPiece.cs | 12 ++------ .../Rulesets/Judgements/JudgementPiece.cs | 29 +++++++++++++++++++ 6 files changed, 39 insertions(+), 46 deletions(-) create mode 100644 osu.Game/Rulesets/Judgements/JudgementPiece.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs index 82d10e500d..5cb03f6536 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs @@ -3,11 +3,9 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -18,20 +16,16 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Skinning.Argon { - public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement + public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement { - protected readonly HitResult Result; - - protected SpriteText JudgementText { get; private set; } = null!; - private RingExplosion? ringExplosion; [Resolved] private OsuColour colours { get; set; } = null!; public ArgonJudgementPiece(HitResult result) + : base(result) { - Result = result; Origin = Anchor.Centre; Y = 160; } @@ -47,7 +41,6 @@ namespace osu.Game.Rulesets.Catch.Skinning.Argon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = Result.GetDescription().ToUpperInvariant(), Colour = colours.ForHitResult(Result), Blending = BlendingParameters.Additive, Spacing = new Vector2(10, 0), diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs index 2dbf475c7e..870a142ca7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs @@ -3,11 +3,9 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -18,20 +16,16 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon { - public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement + public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement { - protected readonly HitResult Result; - - protected SpriteText JudgementText { get; private set; } = null!; - private RingExplosion? ringExplosion; [Resolved] private OsuColour colours { get; set; } = null!; public ArgonJudgementPiece(HitResult result) + : base(result) { - Result = result; Origin = Anchor.Centre; Y = 160; } @@ -47,7 +41,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = Result.GetDescription().ToUpperInvariant(), Colour = colours.ForHitResult(Result), Blending = BlendingParameters.Additive, Spacing = new Vector2(10, 0), diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs index f5f410210b..7b1a0d8d08 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs @@ -3,10 +3,8 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -17,20 +15,16 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Argon { - public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement + public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement { - protected readonly HitResult Result; - - protected SpriteText JudgementText { get; private set; } = null!; - private RingExplosion? ringExplosion; [Resolved] private OsuColour colours { get; set; } = null!; public ArgonJudgementPiece(HitResult result) + : base(result) { - Result = result; Origin = Anchor.Centre; } @@ -45,7 +39,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = Result.GetDescription().ToUpperInvariant(), Colour = colours.ForHitResult(Result), Blending = BlendingParameters.Additive, Spacing = new Vector2(5, 0), diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs index 6756001089..e8240911b0 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs @@ -3,11 +3,9 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -18,20 +16,16 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.Skinning.Argon { - public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement + public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement { - protected readonly HitResult Result; - - protected SpriteText JudgementText { get; private set; } = null!; - private RingExplosion? ringExplosion; [Resolved] private OsuColour colours { get; set; } = null!; public ArgonJudgementPiece(HitResult result) + : base(result) { - Result = result; RelativePositionAxes = Axes.Both; RelativeSizeAxes = Axes.Both; } @@ -45,7 +39,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = Result.GetDescription().ToUpperInvariant(), Colour = colours.ForHitResult(Result), Blending = BlendingParameters.Additive, Spacing = new Vector2(10, 0), diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 2b8bd08ede..485e2b409d 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -4,10 +4,7 @@ #nullable disable using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Scoring; @@ -15,18 +12,14 @@ using osuTK; namespace osu.Game.Rulesets.Judgements { - public partial class DefaultJudgementPiece : CompositeDrawable, IAnimatableJudgement + public partial class DefaultJudgementPiece : JudgementPiece, IAnimatableJudgement { - protected readonly HitResult Result; - - protected SpriteText JudgementText { get; private set; } - [Resolved] private OsuColour colours { get; set; } public DefaultJudgementPiece(HitResult result) + : base(result) { - Result = result; Origin = Anchor.Centre; } @@ -41,7 +34,6 @@ namespace osu.Game.Rulesets.Judgements { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = Result.GetDescription().ToUpperInvariant(), Colour = colours.ForHitResult(Result), Font = OsuFont.Numeric.With(size: 20), Scale = new Vector2(0.85f, 1), diff --git a/osu.Game/Rulesets/Judgements/JudgementPiece.cs b/osu.Game/Rulesets/Judgements/JudgementPiece.cs new file mode 100644 index 0000000000..4e9b495cb5 --- /dev/null +++ b/osu.Game/Rulesets/Judgements/JudgementPiece.cs @@ -0,0 +1,29 @@ +// 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; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Judgements +{ + public abstract partial class JudgementPiece : CompositeDrawable + { + protected readonly HitResult Result; + + protected SpriteText JudgementText { get; set; } = null!; + + protected JudgementPiece(HitResult result) + { + Result = result; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + JudgementText.Text = Result.GetDescription().ToUpperInvariant(); + } + } +} From e8a0f8996cfbf76747647c2ab1a0573007393063 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 03:35:27 +0800 Subject: [PATCH 033/138] Remove unused osu!catch `ArgonJudgementPiece` --- .../Skinning/Argon/ArgonJudgementPiece.cs | 186 ------------------ 1 file changed, 186 deletions(-) delete mode 100644 osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs deleted file mode 100644 index 5cb03f6536..0000000000 --- a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs +++ /dev/null @@ -1,186 +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.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Utils; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Catch.Skinning.Argon -{ - public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement - { - private RingExplosion? ringExplosion; - - [Resolved] - private OsuColour colours { get; set; } = null!; - - public ArgonJudgementPiece(HitResult result) - : base(result) - { - Origin = Anchor.Centre; - Y = 160; - } - - [BackgroundDependencyLoader] - private void load() - { - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - JudgementText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = colours.ForHitResult(Result), - Blending = BlendingParameters.Additive, - Spacing = new Vector2(10, 0), - Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular), - }, - }; - - if (Result.IsHit()) - { - AddInternal(ringExplosion = new RingExplosion(Result) - { - Colour = colours.ForHitResult(Result), - }); - } - } - - /// - /// Plays the default animation for this judgement piece. - /// - /// - /// The base implementation only handles fade (for all result types) and misses. - /// Individual rulesets are recommended to implement their appropriate hit animations. - /// - public virtual void PlayAnimation() - { - switch (Result) - { - default: - JudgementText - .ScaleTo(Vector2.One) - .ScaleTo(new Vector2(1.4f), 1800, Easing.OutQuint); - break; - - case HitResult.Miss: - this.ScaleTo(1.6f); - this.ScaleTo(1, 100, Easing.In); - - this.MoveTo(Vector2.Zero); - this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); - - this.RotateTo(0); - this.RotateTo(40, 800, Easing.InQuint); - break; - } - - this.FadeOutFromOne(800); - - ringExplosion?.PlayAnimation(); - } - - public Drawable? GetAboveHitObjectsProxiedContent() => null; - - private partial class RingExplosion : CompositeDrawable - { - private readonly float travel = 52; - - public RingExplosion(HitResult result) - { - const float thickness = 4; - - const float small_size = 9; - const float large_size = 14; - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Blending = BlendingParameters.Additive; - - int countSmall = 0; - int countLarge = 0; - - switch (result) - { - case HitResult.Meh: - countSmall = 3; - travel *= 0.3f; - break; - - case HitResult.Ok: - case HitResult.Good: - countSmall = 4; - travel *= 0.6f; - break; - - case HitResult.Great: - case HitResult.Perfect: - countSmall = 4; - countLarge = 4; - break; - } - - for (int i = 0; i < countSmall; i++) - AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) }); - - for (int i = 0; i < countLarge; i++) - AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) }); - } - - public void PlayAnimation() - { - foreach (var c in InternalChildren) - { - const float start_position_ratio = 0.3f; - - float direction = RNG.NextSingle(0, 360); - float distance = RNG.NextSingle(travel / 2, travel); - - c.MoveTo(new Vector2( - MathF.Cos(direction) * distance * start_position_ratio, - MathF.Sin(direction) * distance * start_position_ratio - )); - - c.MoveTo(new Vector2( - MathF.Cos(direction) * distance, - MathF.Sin(direction) * distance - ), 600, Easing.OutQuint); - } - - this.FadeOutFromOne(1000, Easing.OutQuint); - } - - public partial class RingPiece : CircularContainer - { - public RingPiece(float thickness = 9) - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Masking = true; - BorderThickness = thickness; - BorderColour = Color4.White; - - Child = new Box - { - AlwaysPresent = true, - Alpha = 0, - RelativeSizeAxes = Axes.Both - }; - } - } - } - } -} From 03603f8b548bc6869dac942543db678edf9ecc7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 03:35:44 +0800 Subject: [PATCH 034/138] Don't show great or higher judgements when using argon "pro" skin --- .../Skinning/Argon/ManiaArgonSkinTransformer.cs | 4 ++++ .../Skinning/Argon/OsuArgonSkinTransformer.cs | 4 ++++ .../Skinning/Argon/TaikoArgonSkinTransformer.cs | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index eb7f63fbe2..057b7eb0d9 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -27,6 +27,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon switch (lookup) { case GameplaySkinComponentLookup resultComponent: + // This should eventually be moved to a skin setting, when supported. + if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great) + return Drawable.Empty(); + return new ArgonJudgementPiece(resultComponent.Component); case ManiaSkinComponentLookup maniaComponent: diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 86194d2c43..f98a47097d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -19,6 +19,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon switch (lookup) { case GameplaySkinComponentLookup resultComponent: + // This should eventually be moved to a skin setting, when supported. + if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great) + return Drawable.Empty(); + return new ArgonJudgementPiece(resultComponent.Component); case OsuSkinComponentLookup osuComponent: diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs index a5d091a1c8..780018af4e 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs @@ -19,6 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon switch (component) { case GameplaySkinComponentLookup resultComponent: + // This should eventually be moved to a skin setting, when supported. + if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great) + return Drawable.Empty(); + return new ArgonJudgementPiece(resultComponent.Component); case TaikoSkinComponentLookup taikoComponent: 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 035/138] 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 036/138] 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 5e9fb1063a9ad5c4775205497cd5a4904f01c699 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 12:22:36 +0800 Subject: [PATCH 037/138] Move judgement text creation to base class and tidy things up --- .../Skinning/Argon/ArgonJudgementPiece.cs | 28 ++++++++--------- .../Skinning/Argon/ArgonJudgementPiece.cs | 28 ++++++++--------- .../Skinning/Argon/ArgonJudgementPiece.cs | 26 ++++++++-------- .../Judgements/DefaultJudgementPiece.cs | 30 +++++++------------ .../Rulesets/Judgements/JudgementPiece.cs | 15 ++++++++-- 5 files changed, 61 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs index 870a142ca7..4ce3c50f7c 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -26,6 +27,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon public ArgonJudgementPiece(HitResult result) : base(result) { + AutoSizeAxes = Axes.Both; + Origin = Anchor.Centre; Y = 160; } @@ -33,21 +36,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon [BackgroundDependencyLoader] private void load() { - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - JudgementText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = colours.ForHitResult(Result), - Blending = BlendingParameters.Additive, - Spacing = new Vector2(10, 0), - Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular), - }, - }; - if (Result.IsHit()) { AddInternal(ringExplosion = new RingExplosion(Result) @@ -57,6 +45,16 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon } } + protected override SpriteText CreateJudgementText() => + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + Spacing = new Vector2(10, 0), + Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular), + }; + /// /// Plays the default animation for this judgement piece. /// diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs index 7b1a0d8d08..6f55d93eff 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -25,27 +26,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon public ArgonJudgementPiece(HitResult result) : base(result) { + AutoSizeAxes = Axes.Both; + Origin = Anchor.Centre; } [BackgroundDependencyLoader] private void load() { - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - JudgementText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = colours.ForHitResult(Result), - Blending = BlendingParameters.Additive, - Spacing = new Vector2(5, 0), - Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold), - }, - }; - if (Result.IsHit()) { AddInternal(ringExplosion = new RingExplosion(Result) @@ -55,6 +43,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon } } + protected override SpriteText CreateJudgementText() => + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + Spacing = new Vector2(5, 0), + Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold), + }; + /// /// Plays the default animation for this judgement piece. /// diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs index e8240911b0..bbd62ff85b 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -33,20 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] - { - JudgementText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = colours.ForHitResult(Result), - Blending = BlendingParameters.Additive, - Spacing = new Vector2(10, 0), - RelativePositionAxes = Axes.Both, - Font = OsuFont.Default.With(size: 20, weight: FontWeight.Regular), - }, - }; - if (Result.IsHit()) { AddInternal(ringExplosion = new RingExplosion(Result) @@ -57,6 +44,17 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon } } + protected override SpriteText CreateJudgementText() => + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + Spacing = new Vector2(10, 0), + RelativePositionAxes = Axes.Both, + Font = OsuFont.Default.With(size: 20, weight: FontWeight.Regular), + }; + /// /// Plays the default animation for this judgement piece. /// diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 485e2b409d..6551752826 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -1,10 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Scoring; @@ -15,31 +14,24 @@ namespace osu.Game.Rulesets.Judgements public partial class DefaultJudgementPiece : JudgementPiece, IAnimatableJudgement { [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; public DefaultJudgementPiece(HitResult result) : base(result) { + AutoSizeAxes = Axes.Both; + Origin = Anchor.Centre; } - [BackgroundDependencyLoader] - private void load() - { - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] + protected override SpriteText CreateJudgementText() => + new OsuSpriteText { - JudgementText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = colours.ForHitResult(Result), - Font = OsuFont.Numeric.With(size: 20), - Scale = new Vector2(0.85f, 1), - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Numeric.With(size: 20), + Scale = new Vector2(0.85f, 1), }; - } /// /// Plays the default animation for this judgement piece. @@ -67,6 +59,6 @@ namespace osu.Game.Rulesets.Judgements this.FadeOutFromOne(800); } - public Drawable GetAboveHitObjectsProxiedContent() => null; + public Drawable? GetAboveHitObjectsProxiedContent() => null; } } diff --git a/osu.Game/Rulesets/Judgements/JudgementPiece.cs b/osu.Game/Rulesets/Judgements/JudgementPiece.cs index 4e9b495cb5..9c31c6aa34 100644 --- a/osu.Game/Rulesets/Judgements/JudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/JudgementPiece.cs @@ -1,9 +1,11 @@ // 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.Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Judgements @@ -12,18 +14,25 @@ namespace osu.Game.Rulesets.Judgements { protected readonly HitResult Result; - protected SpriteText JudgementText { get; set; } = null!; + protected SpriteText JudgementText { get; private set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; protected JudgementPiece(HitResult result) { Result = result; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); + JudgementText = CreateJudgementText(); + JudgementText.Colour = colours.ForHitResult(Result); JudgementText.Text = Result.GetDescription().ToUpperInvariant(); } + + protected abstract SpriteText CreateJudgementText(); } } From 91bde14fb345c034589738e8361fd5ad39df991a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 15:42:22 +0800 Subject: [PATCH 038/138] Add button to settings to show lazer upgrade guide --- .../Settings/Sections/GeneralSection.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index c62d44fd30..2ca932f2ac 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.General; @@ -15,7 +14,10 @@ namespace osu.Game.Overlays.Settings.Sections public partial class GeneralSection : SettingsSection { [Resolved(CanBeNull = true)] - private FirstRunSetupOverlay firstRunSetupOverlay { get; set; } + private FirstRunSetupOverlay? firstRunSetupOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private OsuGame? game { get; set; } public override LocalisableString Header => GeneralSettingsStrings.GeneralSectionHeader; @@ -24,15 +26,24 @@ namespace osu.Game.Overlays.Settings.Sections Icon = FontAwesome.Solid.Cog }; - public GeneralSection() + [BackgroundDependencyLoader] + private void load(OsuColour colours) { Children = new Drawable[] { new SettingsButton { Text = GeneralSettingsStrings.RunSetupWizard, + TooltipText = FirstRunSetupOverlayStrings.FirstRunSetupDescription, Action = () => firstRunSetupOverlay?.Show(), }, + new SettingsButton + { + Text = "Learn more about lazer", + TooltipText = "Check out the feature comparison and FAQ", + BackgroundColour = colours.YellowDark, + Action = () => game?.ShowWiki(@"Help_centre/Upgrading_to_lazer") + }, new LanguageSettings(), new UpdateSettings(), }; From f973befcd4f0f24701917117b5a9fe41d345b7fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 09:34:30 +0100 Subject: [PATCH 039/138] Remove unused resolved member --- osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 6551752826..d5f586dc35 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -1,7 +1,6 @@ // 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.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; @@ -13,9 +12,6 @@ namespace osu.Game.Rulesets.Judgements { public partial class DefaultJudgementPiece : JudgementPiece, IAnimatableJudgement { - [Resolved] - private OsuColour colours { get; set; } = null!; - public DefaultJudgementPiece(HitResult result) : base(result) { From 80de5dac66776d9da6348ff60421bb1e0c66a49f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 09:37:40 +0100 Subject: [PATCH 040/138] Fix judgement text never being added to hierarchy --- osu.Game/Rulesets/Judgements/JudgementPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/JudgementPiece.cs b/osu.Game/Rulesets/Judgements/JudgementPiece.cs index 9c31c6aa34..03f211c318 100644 --- a/osu.Game/Rulesets/Judgements/JudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/JudgementPiece.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Judgements [BackgroundDependencyLoader] private void load() { - JudgementText = CreateJudgementText(); + AddInternal(JudgementText = CreateJudgementText()); JudgementText.Colour = colours.ForHitResult(Result); JudgementText.Text = Result.GetDescription().ToUpperInvariant(); 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 041/138] 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 2c060ac8d44bbf0857e3177cbfd9ab4babf332fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 17:32:04 +0800 Subject: [PATCH 042/138] Add localisation support for new button's strings --- osu.Game/Localisation/GeneralSettingsStrings.cs | 10 ++++++++++ osu.Game/Overlays/Settings/Sections/GeneralSection.cs | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/GeneralSettingsStrings.cs b/osu.Game/Localisation/GeneralSettingsStrings.cs index 3278b20983..a525af508b 100644 --- a/osu.Game/Localisation/GeneralSettingsStrings.cs +++ b/osu.Game/Localisation/GeneralSettingsStrings.cs @@ -64,6 +64,16 @@ namespace osu.Game.Localisation /// public static LocalisableString RunSetupWizard => new TranslatableString(getKey(@"run_setup_wizard"), @"Run setup wizard"); + /// + /// "Learn more about lazer" + /// + public static LocalisableString LearnMoreAboutLazer => new TranslatableString(getKey(@"learn_more_about_lazer"), @"Learn more about lazer"); + + /// + /// "Check out the feature comparison and FAQ" + /// + public static LocalisableString LearnMoreAboutLazerTooltip => new TranslatableString(getKey(@"check_out_the_feature_comparison"), @"Check out the feature comparison and FAQ"); + /// /// "You are running the latest release ({0})" /// diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index 2ca932f2ac..84c1f3cfb6 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -39,8 +39,8 @@ namespace osu.Game.Overlays.Settings.Sections }, new SettingsButton { - Text = "Learn more about lazer", - TooltipText = "Check out the feature comparison and FAQ", + Text = GeneralSectionStrings.LearnMoreAboutLazer, + TooltipText = GeneralSectionStrings.CheckOutTheFeatureComparison, BackgroundColour = colours.YellowDark, Action = () => game?.ShowWiki(@"Help_centre/Upgrading_to_lazer") }, From 301eb71e22dc81e88d0537ac1299ce224648f81c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 10:39:05 +0100 Subject: [PATCH 043/138] Fix wrong member names --- osu.Game/Overlays/Settings/Sections/GeneralSection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index 84c1f3cfb6..d4fd78f0c8 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -39,8 +39,8 @@ namespace osu.Game.Overlays.Settings.Sections }, new SettingsButton { - Text = GeneralSectionStrings.LearnMoreAboutLazer, - TooltipText = GeneralSectionStrings.CheckOutTheFeatureComparison, + Text = GeneralSettingsStrings.LearnMoreAboutLazer, + TooltipText = GeneralSettingsStrings.LearnMoreAboutLazerTooltip, BackgroundColour = colours.YellowDark, Action = () => game?.ShowWiki(@"Help_centre/Upgrading_to_lazer") }, 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 044/138] 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 d6e079a2b4e09cb80a30189c84a57c9eb3605f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 11:28:46 +0100 Subject: [PATCH 045/138] Ignore statistics update requests from third-party rulesets for now --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 48f39504a3..1209747132 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -51,6 +52,9 @@ namespace osu.Game.Online.Solo if (!api.IsLoggedIn) return; + if (!score.Ruleset.IsLegacyRuleset()) + return; + var callback = new StatisticsUpdateCallback(score, onUpdateReady); if (lastProcessedScoreId == score.OnlineID) From fd9110a61e8424384ad90fbea0def6101e3ed430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 13:28:25 +0100 Subject: [PATCH 046/138] Fix solo statistics watcher firing requests for invalid user with id 1 Can happen during login flow (see `APIAccess.attemptConnect()`). --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 1209747132..0dfe8ebb8d 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; @@ -72,11 +71,9 @@ namespace osu.Game.Online.Solo lastProcessedScoreId = null; latestStatistics.Clear(); - if (!api.IsLoggedIn) + if (localUser == null || localUser.OnlineID <= 1) return; - Debug.Assert(localUser != null); - var userRequest = new GetUsersRequest(new[] { localUser.OnlineID }); userRequest.Success += response => Schedule(() => { From 3c26016b61bd43e85c386c69e447665aece584e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 13:30:54 +0100 Subject: [PATCH 047/138] Ensure latest stats are cleared on successful profile fetch --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 0dfe8ebb8d..268483ab91 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -77,6 +77,7 @@ namespace osu.Game.Online.Solo var userRequest = new GetUsersRequest(new[] { localUser.OnlineID }); userRequest.Success += response => Schedule(() => { + latestStatistics.Clear(); foreach (var rulesetStats in response.Users.Single().RulesetsStatistics) latestStatistics.Add(rulesetStats.Key, rulesetStats.Value); }); From 6c4ca387e01fcbbe82b1b29c49e0b0011f2cb1d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 13:42:32 +0100 Subject: [PATCH 048/138] Fix wrong handling of missing ruleset statistics --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 268483ab91..383f6a202d 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -31,7 +31,7 @@ namespace osu.Game.Online.Solo private readonly Dictionary callbacks = new Dictionary(); private long? lastProcessedScoreId; - private readonly Dictionary latestStatistics = new Dictionary(); + private Dictionary? latestStatistics; protected override void LoadComplete() { @@ -69,7 +69,7 @@ namespace osu.Game.Online.Solo { callbacks.Clear(); lastProcessedScoreId = null; - latestStatistics.Clear(); + latestStatistics = null; if (localUser == null || localUser.OnlineID <= 1) return; @@ -77,7 +77,7 @@ namespace osu.Game.Online.Solo var userRequest = new GetUsersRequest(new[] { localUser.OnlineID }); userRequest.Success += response => Schedule(() => { - latestStatistics.Clear(); + latestStatistics = new Dictionary(); foreach (var rulesetStats in response.Users.Single().RulesetsStatistics) latestStatistics.Add(rulesetStats.Key, rulesetStats.Value); }); @@ -109,9 +109,12 @@ namespace osu.Game.Online.Solo { string rulesetName = callback.Score.Ruleset.ShortName; - if (!latestStatistics.TryGetValue(rulesetName, out var latestRulesetStatistics)) + if (latestStatistics == null) return; + latestStatistics.TryGetValue(rulesetName, out UserStatistics? latestRulesetStatistics); + latestRulesetStatistics ??= new UserStatistics(); + var update = new SoloStatisticsUpdate(callback.Score, latestRulesetStatistics, updatedStatistics); callback.OnUpdateReady.Invoke(update); From 78c47a3695a9e8c1bbfa1ef8f07eb7ae2f3df5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 13:45:04 +0100 Subject: [PATCH 049/138] Add callback to dictionary rather than overwrite Attempting to overwrite will henceforth throw an exception. --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 383f6a202d..33344044b9 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -62,7 +62,7 @@ namespace osu.Game.Online.Solo return; } - callbacks[score.OnlineID] = callback; + callbacks.Add(score.OnlineID, callback); }); private void onUserChanged(APIUser? localUser) => Schedule(() => From 600ada46be2099891910cc303b3c1113c67f7fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 10:35:42 +0100 Subject: [PATCH 050/138] Add protected method for customising statistics panel --- osu.Game/Screens/Ranking/ResultsScreen.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index f3aca43a9d..78239e0dbe 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -96,11 +96,11 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - statisticsPanel = new StatisticsPanel + statisticsPanel = CreateStatisticsPanel().With(panel => { - RelativeSizeAxes = Axes.Both, - Score = { BindTarget = SelectedScore } - }, + panel.RelativeSizeAxes = Axes.Both; + panel.Score.BindTarget = SelectedScore; + }), ScorePanelList = new ScorePanelList { RelativeSizeAxes = Axes.Both, @@ -231,6 +231,11 @@ namespace osu.Game.Screens.Ranking /// An responsible for the fetch operation. This will be queued and performed automatically. protected virtual APIRequest FetchNextPage(int direction, Action> scoresCallback) => null; + /// + /// Creates the to be used to display extended information about scores. + /// + protected virtual StatisticsPanel CreateStatisticsPanel() => new StatisticsPanel(); + private void fetchScoresCallback(IEnumerable scores) => Schedule(() => { foreach (var s in scores) From 3abdf557eaf431ac5643254665caf1b8c44df21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 10:51:09 +0100 Subject: [PATCH 051/138] Add protected method for customising statistics panel rows --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 91102d6647..4c22afd8f7 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Ranking.Statistics bool hitEventsAvailable = newScore.HitEvents.Count != 0; Container container; - var statisticRows = newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, task.GetResultSafely()); + var statisticRows = CreateStatisticRows(newScore, task.GetResultSafely()); if (!hitEventsAvailable && statisticRows.SelectMany(r => r.Columns).All(c => c.RequiresHitEvents)) { @@ -218,6 +218,14 @@ namespace osu.Game.Screens.Ranking.Statistics }), localCancellationSource.Token); } + /// + /// Creates the s to be displayed in this panel for a given . + /// + /// The score to create the rows for. + /// The beatmap on which the score was set. + protected virtual ICollection CreateStatisticRows(ScoreInfo newScore, IBeatmap playableBeatmap) + => newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap); + protected override bool OnClick(ClickEvent e) { ToggleVisibility(); From da519acb20efa3f91eb504c1e1eb4fcf2a68ed2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 10:58:38 +0100 Subject: [PATCH 052/138] Add overall ranking display to solo results --- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 20 ++++++++ .../Ranking/Statistics/SoloStatisticsPanel.cs | 51 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 3774cf16b1..6d4feeb0db 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -7,11 +7,14 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Screens.Ranking { @@ -22,11 +25,28 @@ namespace osu.Game.Screens.Ranking [Resolved] private RulesetStore rulesets { get; set; } + [Resolved] + private SoloStatisticsWatcher soloStatisticsWatcher { get; set; } + + private readonly Bindable statisticsUpdate = new Bindable(); + public SoloResultsScreen(ScoreInfo score, bool allowRetry) : base(score, allowRetry) { } + protected override void LoadComplete() + { + base.LoadComplete(); + + soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); + } + + protected override StatisticsPanel CreateStatisticsPanel() => new SoloStatisticsPanel(Score) + { + StatisticsUpdate = { BindTarget = statisticsUpdate } + }; + protected override APIRequest FetchScores(Action> scoresCallback) { if (Score.BeatmapInfo.OnlineID <= 0 || Score.BeatmapInfo.Status <= BeatmapOnlineStatus.Pending) diff --git a/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs new file mode 100644 index 0000000000..4741ea2ea3 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Online.Solo; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics.User; + +namespace osu.Game.Screens.Ranking.Statistics +{ + public partial class SoloStatisticsPanel : StatisticsPanel + { + private readonly ScoreInfo achievedScore; + + public SoloStatisticsPanel(ScoreInfo achievedScore) + { + this.achievedScore = achievedScore; + } + + public Bindable StatisticsUpdate { get; } = new Bindable(); + + protected override ICollection CreateStatisticRows(ScoreInfo newScore, IBeatmap playableBeatmap) + { + var rows = base.CreateStatisticRows(newScore, playableBeatmap); + + if (newScore.UserID == achievedScore.UserID && newScore.OnlineID == achievedScore.OnlineID) + { + rows = rows.Append(new StatisticRow + { + Columns = new[] + { + new StatisticItem("Overall Ranking", () => new OverallRanking + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + StatisticsUpdate = { BindTarget = StatisticsUpdate } + }) + } + }).ToArray(); + } + + return rows; + } + } +} From 145130ba80f21d778c7b3f5d6aa2b95b989b5222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 11:20:31 +0100 Subject: [PATCH 053/138] Register solo statistics watcher at game level --- osu.Game/OsuGameBase.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7586dc6407..36fd5a4177 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -46,6 +46,7 @@ using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Online.Metadata; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Solo; using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Overlays.Settings; @@ -193,6 +194,7 @@ namespace osu.Game protected MultiplayerClient MultiplayerClient { get; private set; } private MetadataClient metadataClient; + private SoloStatisticsWatcher soloStatisticsWatcher; private RealmAccess realm; @@ -301,6 +303,7 @@ namespace osu.Game dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); + dependencies.CacheAs(soloStatisticsWatcher = new SoloStatisticsWatcher()); AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient)); @@ -346,6 +349,7 @@ namespace osu.Game AddInternal(spectatorClient); AddInternal(MultiplayerClient); AddInternal(metadataClient); + AddInternal(soloStatisticsWatcher); AddInternal(rulesetConfigCache); From 36a6f3685ebdf6a50d3fb40ec2268fce0f23589b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 11:27:42 +0100 Subject: [PATCH 054/138] Don't show global rankings display when not logged in --- osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs index 4741ea2ea3..57d072b7de 100644 --- a/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs @@ -27,7 +27,10 @@ namespace osu.Game.Screens.Ranking.Statistics { var rows = base.CreateStatisticRows(newScore, playableBeatmap); - if (newScore.UserID == achievedScore.UserID && newScore.OnlineID == achievedScore.OnlineID) + if (newScore.UserID > 1 + && newScore.UserID == achievedScore.UserID + && newScore.OnlineID > 0 + && newScore.OnlineID == achievedScore.OnlineID) { rows = rows.Append(new StatisticRow { From 8c7814aaf0d296886644206b8633cce64e76ff12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 21:48:04 +0800 Subject: [PATCH 055/138] 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 { From 9d073f42283f4da57e1b219a34b22c062a14b59d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Dec 2022 11:26:09 -0800 Subject: [PATCH 056/138] Link beatmap set title and artist to listing search --- osu.Game/OsuGame.cs | 5 +++- .../BeatmapSet/BeatmapSetHeaderContent.cs | 29 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index de9a009f44..b55b943023 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -353,7 +353,10 @@ namespace osu.Game break; case LinkAction.SearchBeatmapSet: - SearchBeatmapSet(argString); + if (link.Argument is RomanisableString romanisable) + SearchBeatmapSet(romanisable.GetPreferred(frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode).Value)); + else + SearchBeatmapSet(argString); break; case LinkAction.OpenEditorTimestamp: diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 0318dad0e3..6977110ad4 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -16,11 +16,12 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; using osu.Game.Overlays.BeatmapSet.Buttons; using osuTK; @@ -41,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapSet private readonly UpdateableOnlineBeatmapSetCover cover; private readonly Box coverGradient; - private readonly OsuSpriteText title, artist; + private readonly LinkFlowContainer title, artist; private readonly AuthorInfo author; private readonly ExplicitContentBeatmapBadge explicitContent; @@ -127,9 +128,12 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Top = 15 }, Children = new Drawable[] { - title = new OsuSpriteText + title = new LinkFlowContainer(s => { - Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true) + s.Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true); + }) + { + AutoSizeAxes = Axes.Both, }, externalLink = new ExternalLinkButton { @@ -160,9 +164,12 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Bottom = 20 }, Children = new Drawable[] { - artist = new OsuSpriteText + artist = new LinkFlowContainer(s => { - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true), + s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true); + }) + { + AutoSizeAxes = Axes.Both, }, featuredArtist = new FeaturedArtistBeatmapBadge { @@ -275,8 +282,14 @@ namespace osu.Game.Overlays.BeatmapSet loading.Hide(); - title.Text = new RomanisableString(setInfo.NewValue.TitleUnicode, setInfo.NewValue.Title); - artist.Text = new RomanisableString(setInfo.NewValue.ArtistUnicode, setInfo.NewValue.Artist); + var titleText = new RomanisableString(setInfo.NewValue.TitleUnicode, setInfo.NewValue.Title); + var artistText = new RomanisableString(setInfo.NewValue.ArtistUnicode, setInfo.NewValue.Artist); + + title.Clear(); + artist.Clear(); + + title.AddLink(titleText, LinkAction.SearchBeatmapSet, titleText); + artist.AddLink(artistText, LinkAction.SearchBeatmapSet, artistText); explicitContent.Alpha = setInfo.NewValue.HasExplicitContent ? 1 : 0; spotlight.Alpha = setInfo.NewValue.FeaturedInSpotlight ? 1 : 0; From df645ef3cb8de16a67ac8e92831830bf10d5da16 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Dec 2022 11:42:05 -0800 Subject: [PATCH 057/138] Change title/artist idle colour to white --- .../BeatmapSet/BeatmapSetHeaderContent.cs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 6977110ad4..357c9bc55d 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,6 +13,7 @@ using osu.Framework.Graphics.Colour; using osu.Game.Graphics.Cursor; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -24,6 +26,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays.BeatmapSet.Buttons; using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet { @@ -128,7 +131,7 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Top = 15 }, Children = new Drawable[] { - title = new LinkFlowContainer(s => + title = new MetadataFlowContainer(s => { s.Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true); }) @@ -164,7 +167,7 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Bottom = 20 }, Children = new Drawable[] { - artist = new LinkFlowContainer(s => + artist = new MetadataFlowContainer(s => { s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true); }) @@ -340,5 +343,29 @@ namespace osu.Game.Overlays.BeatmapSet break; } } + + public partial class MetadataFlowContainer : LinkFlowContainer + { + public MetadataFlowContainer(Action defaultCreationParameters = null) + : base(defaultCreationParameters) + { + } + + protected override DrawableLinkCompiler CreateLinkCompiler(ITextPart textPart) => new MetadataLinkCompiler(textPart); + + public partial class MetadataLinkCompiler : DrawableLinkCompiler + { + public MetadataLinkCompiler(ITextPart part) + : base(part) + { + } + + [BackgroundDependencyLoader] + private void load() + { + IdleColour = Color4.White; + } + } + } } } From 4f6b3644f3208293b1e659d79942de42a5505561 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Dec 2022 12:40:32 -0800 Subject: [PATCH 058/138] Fix title/artist overflowing to right side --- .../BeatmapSet/BeatmapSetHeaderContent.cs | 121 +++++++++--------- 1 file changed, 62 insertions(+), 59 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 357c9bc55d..b305e6ef05 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -48,9 +48,10 @@ namespace osu.Game.Overlays.BeatmapSet private readonly LinkFlowContainer title, artist; private readonly AuthorInfo author; - private readonly ExplicitContentBeatmapBadge explicitContent; - private readonly SpotlightBeatmapBadge spotlight; - private readonly FeaturedArtistBeatmapBadge featuredArtist; + private ExplicitContentBeatmapBadge explicitContent; + private SpotlightBeatmapBadge spotlight; + private FeaturedArtistBeatmapBadge featuredArtist; + private ExternalLinkButton externalLink; private readonly FillFlowContainer downloadButtonsContainer; private readonly BeatmapAvailability beatmapAvailability; @@ -69,8 +70,6 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapSetHeaderContent() { - ExternalLinkButton externalLink; - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; InternalChild = new Container @@ -124,64 +123,19 @@ namespace osu.Game.Overlays.BeatmapSet AutoSizeAxes = Axes.Y, Child = Picker = new BeatmapPicker(), }, - new FillFlowContainer + title = new MetadataFlowContainer(s => + { + s.Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true); + }) { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Top = 15 }, - Children = new Drawable[] - { - title = new MetadataFlowContainer(s => - { - s.Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true); - }) - { - AutoSizeAxes = Axes.Both, - }, - externalLink = new ExternalLinkButton - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Left = 5, Bottom = 4 }, // To better lineup with the font - }, - explicitContent = new ExplicitContentBeatmapBadge - { - Alpha = 0f, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Left = 10, Bottom = 4 }, - }, - spotlight = new SpotlightBeatmapBadge - { - Alpha = 0f, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Left = 10, Bottom = 4 }, - } - } }, - new FillFlowContainer + artist = new MetadataFlowContainer(s => + { + s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true); + }) { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Bottom = 20 }, - Children = new Drawable[] - { - artist = new MetadataFlowContainer(s => - { - s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true); - }) - { - AutoSizeAxes = Axes.Both, - }, - featuredArtist = new FeaturedArtistBeatmapBadge - { - Alpha = 0f, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Left = 10 } - } - } }, new Container { @@ -247,12 +201,17 @@ namespace osu.Game.Overlays.BeatmapSet Picker.Beatmap.ValueChanged += b => { Details.BeatmapInfo = b.NewValue; - externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineID}#{b.NewValue?.Ruleset.ShortName}/{b.NewValue?.OnlineID}"; + updateExternalLink(); onlineStatusPill.Status = b.NewValue?.Status ?? BeatmapOnlineStatus.None; }; } + private void updateExternalLink() + { + if (externalLink != null) externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineID}#{Picker.Beatmap.Value?.Ruleset.ShortName}/{Picker.Beatmap.Value?.OnlineID}"; + } + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { @@ -292,8 +251,49 @@ namespace osu.Game.Overlays.BeatmapSet artist.Clear(); title.AddLink(titleText, LinkAction.SearchBeatmapSet, titleText); + + title.AddArbitraryDrawable(new Container + { + AutoSizeAxes = Axes.Both, + Child = externalLink = new ExternalLinkButton + { + Margin = new MarginPadding { Left = 5 }, + } + }); + + title.AddArbitraryDrawable(new Container + { + AutoSizeAxes = Axes.Both, + Child = explicitContent = new ExplicitContentBeatmapBadge + { + Alpha = 0f, + Margin = new MarginPadding { Left = 10 }, + } + }); + + title.AddArbitraryDrawable(new Container + { + AutoSizeAxes = Axes.Both, + Child = spotlight = new SpotlightBeatmapBadge + { + Alpha = 0f, + Margin = new MarginPadding { Left = 10 }, + } + }); + artist.AddLink(artistText, LinkAction.SearchBeatmapSet, artistText); + artist.AddArbitraryDrawable(new Container + { + AutoSizeAxes = Axes.Both, + Child = featuredArtist = new FeaturedArtistBeatmapBadge + { + Alpha = 0f, + Margin = new MarginPadding { Left = 10 } + } + }); + + updateExternalLink(); explicitContent.Alpha = setInfo.NewValue.HasExplicitContent ? 1 : 0; spotlight.Alpha = setInfo.NewValue.FeaturedInSpotlight ? 1 : 0; featuredArtist.Alpha = setInfo.NewValue.TrackId != null ? 1 : 0; @@ -349,6 +349,9 @@ namespace osu.Game.Overlays.BeatmapSet public MetadataFlowContainer(Action defaultCreationParameters = null) : base(defaultCreationParameters) { + TextAnchor = Anchor.CentreLeft; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; } protected override DrawableLinkCompiler CreateLinkCompiler(ITextPart textPart) => new MetadataLinkCompiler(textPart); From ae967e08b0c3cd07714d397c9bf0a58224b63b53 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Dec 2022 13:27:46 -0800 Subject: [PATCH 059/138] Add badges when needed instead of using alpha --- .../BeatmapSet/BeatmapSetHeaderContent.cs | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index b305e6ef05..043844b56b 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -261,42 +261,36 @@ namespace osu.Game.Overlays.BeatmapSet } }); - title.AddArbitraryDrawable(new Container + if (setInfo.NewValue.HasExplicitContent) { - AutoSizeAxes = Axes.Both, - Child = explicitContent = new ExplicitContentBeatmapBadge + title.AddArbitraryDrawable(new Container { - Alpha = 0f, - Margin = new MarginPadding { Left = 10 }, - } - }); + AutoSizeAxes = Axes.Both, + Child = explicitContent = new ExplicitContentBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, + }); + } - title.AddArbitraryDrawable(new Container + if (setInfo.NewValue.FeaturedInSpotlight) { - AutoSizeAxes = Axes.Both, - Child = spotlight = new SpotlightBeatmapBadge + title.AddArbitraryDrawable(new Container { - Alpha = 0f, - Margin = new MarginPadding { Left = 10 }, - } - }); + AutoSizeAxes = Axes.Both, + Child = spotlight = new SpotlightBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, + }); + } artist.AddLink(artistText, LinkAction.SearchBeatmapSet, artistText); - artist.AddArbitraryDrawable(new Container + if (setInfo.NewValue.TrackId != null) { - AutoSizeAxes = Axes.Both, - Child = featuredArtist = new FeaturedArtistBeatmapBadge + artist.AddArbitraryDrawable(new Container { - Alpha = 0f, - Margin = new MarginPadding { Left = 10 } - } - }); + AutoSizeAxes = Axes.Both, + Child = featuredArtist = new FeaturedArtistBeatmapBadge { Margin = new MarginPadding { Left = 10 } } + }); + } updateExternalLink(); - explicitContent.Alpha = setInfo.NewValue.HasExplicitContent ? 1 : 0; - spotlight.Alpha = setInfo.NewValue.FeaturedInSpotlight ? 1 : 0; - featuredArtist.Alpha = setInfo.NewValue.TrackId != null ? 1 : 0; onlineStatusPill.FadeIn(500, Easing.OutQuint); From b871d6f078ebfff9eb859011693de8f439d41462 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Dec 2022 13:35:17 -0800 Subject: [PATCH 060/138] Remove unused fields --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 043844b56b..13506bfd1c 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -48,9 +48,6 @@ namespace osu.Game.Overlays.BeatmapSet private readonly LinkFlowContainer title, artist; private readonly AuthorInfo author; - private ExplicitContentBeatmapBadge explicitContent; - private SpotlightBeatmapBadge spotlight; - private FeaturedArtistBeatmapBadge featuredArtist; private ExternalLinkButton externalLink; private readonly FillFlowContainer downloadButtonsContainer; @@ -266,7 +263,7 @@ namespace osu.Game.Overlays.BeatmapSet title.AddArbitraryDrawable(new Container { AutoSizeAxes = Axes.Both, - Child = explicitContent = new ExplicitContentBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, + Child = new ExplicitContentBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, }); } @@ -275,7 +272,7 @@ namespace osu.Game.Overlays.BeatmapSet title.AddArbitraryDrawable(new Container { AutoSizeAxes = Axes.Both, - Child = spotlight = new SpotlightBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, + Child = new SpotlightBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, }); } @@ -286,7 +283,7 @@ namespace osu.Game.Overlays.BeatmapSet artist.AddArbitraryDrawable(new Container { AutoSizeAxes = Axes.Both, - Child = featuredArtist = new FeaturedArtistBeatmapBadge { Margin = new MarginPadding { Left = 10 } } + Child = new FeaturedArtistBeatmapBadge { Margin = new MarginPadding { Left = 10 } } }); } From e6f9d6202c4dd174ba21804d17c41fb185f73837 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Dec 2022 23:40:01 +0800 Subject: [PATCH 061/138] Update appveyor deploy image --- appveyor_deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml index adf98848bc..175c8d0f1b 100644 --- a/appveyor_deploy.yml +++ b/appveyor_deploy.yml @@ -1,6 +1,6 @@ clone_depth: 1 version: '{build}' -image: Visual Studio 2019 +image: Visual Studio 2022 test: off skip_non_tags: true configuration: Release @@ -83,4 +83,4 @@ artifacts: deploy: - provider: Environment - name: nuget \ No newline at end of file + name: nuget From 8e899c2e9278db719507918405e7d2c315ae93c9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 25 Dec 2022 16:03:44 -0800 Subject: [PATCH 062/138] Use localisation parameters to find preferred string instead --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b55b943023..983277135d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -354,7 +354,7 @@ namespace osu.Game case LinkAction.SearchBeatmapSet: if (link.Argument is RomanisableString romanisable) - SearchBeatmapSet(romanisable.GetPreferred(frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode).Value)); + SearchBeatmapSet(romanisable.GetPreferred(Localisation.CurrentParameters.Value.PreferOriginalScript)); else SearchBeatmapSet(argString); break; From f959b02dc85d750b2d05ec13183d548e66e4252c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 25 Dec 2022 16:05:59 -0800 Subject: [PATCH 063/138] Use empty drawables for spacing badges instead --- .../BeatmapSet/BeatmapSetHeaderContent.cs | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 13506bfd1c..17836b558c 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -249,42 +249,27 @@ namespace osu.Game.Overlays.BeatmapSet title.AddLink(titleText, LinkAction.SearchBeatmapSet, titleText); - title.AddArbitraryDrawable(new Container - { - AutoSizeAxes = Axes.Both, - Child = externalLink = new ExternalLinkButton - { - Margin = new MarginPadding { Left = 5 }, - } - }); + title.AddArbitraryDrawable(Empty().With(d => d.Width = 5)); + title.AddArbitraryDrawable(externalLink = new ExternalLinkButton()); if (setInfo.NewValue.HasExplicitContent) { - title.AddArbitraryDrawable(new Container - { - AutoSizeAxes = Axes.Both, - Child = new ExplicitContentBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, - }); + title.AddArbitraryDrawable(Empty().With(d => d.Width = 10)); + title.AddArbitraryDrawable(new ExplicitContentBeatmapBadge()); } if (setInfo.NewValue.FeaturedInSpotlight) { - title.AddArbitraryDrawable(new Container - { - AutoSizeAxes = Axes.Both, - Child = new SpotlightBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, - }); + title.AddArbitraryDrawable(Empty().With(d => d.Width = 10)); + title.AddArbitraryDrawable(new SpotlightBeatmapBadge()); } artist.AddLink(artistText, LinkAction.SearchBeatmapSet, artistText); if (setInfo.NewValue.TrackId != null) { - artist.AddArbitraryDrawable(new Container - { - AutoSizeAxes = Axes.Both, - Child = new FeaturedArtistBeatmapBadge { Margin = new MarginPadding { Left = 10 } } - }); + artist.AddArbitraryDrawable(Empty().With(d => d.Width = 10)); + artist.AddArbitraryDrawable(new FeaturedArtistBeatmapBadge()); } updateExternalLink(); From 973fd90af2b334229e89f87aa31790daa6bef2f1 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 25 Dec 2022 16:15:02 -0800 Subject: [PATCH 064/138] Fix parameters with the same default value inspection --- osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs | 4 ++-- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 2 +- osu.Game/Graphics/OsuFont.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 32d0cc8939..1e9f931b74 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -160,9 +160,9 @@ namespace osu.Game.Rulesets.Osu.Tests static bool assertSamples(HitObject hitObject) => hitObject.Samples.All(s => s.Name != HitSampleInfo.HIT_CLAP && s.Name != HitSampleInfo.HIT_WHISTLE); } - private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); + private Drawable testSimpleBig(int repeats = 0) => createSlider(repeats: repeats); - private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10); + private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(repeats: repeats, stackHeight: 10); private Drawable testDistanceOverflow(int repeats = 0) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 2fbdfbc198..d5031bc606 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -257,7 +257,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTestAPI(true); - createPlayerTest(false, createRuleset: () => new OsuRuleset + createPlayerTest(createRuleset: () => new OsuRuleset { RulesetInfo = { diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 038ea0f5d7..12e82469e8 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -21,7 +21,7 @@ namespace osu.Game.Graphics public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Bold); - public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular); + public static FontUsage Torus => GetFont(weight: FontWeight.Regular); public static FontUsage TorusAlternate => GetFont(Typeface.TorusAlternate, weight: FontWeight.Regular); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 006eec2838..425f40258e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -95,8 +95,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersScore, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy, Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, minSize: 60, maxSize: 70)), new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 25)), // flag - new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersPlayer, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 125)), - new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersCombo, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120)) + new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersPlayer, Anchor.CentreLeft, new Dimension(minSize: 125)), + new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersCombo, Anchor.CentreLeft, new Dimension(minSize: 70, maxSize: 120)) }; // All statistics across all scores, unordered. @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var displayName = ruleset.GetDisplayNameForHitResult(result); - columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); + columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(minSize: 35, maxSize: 60))); statisticResultTypes.Add((result, displayName)); } From a10628e2702a7dbda3d38b35c422928f70416a73 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 25 Dec 2022 20:46:59 -0800 Subject: [PATCH 065/138] Change severity of `RedundantArgumentDefaultValue` to hint --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index ef3b08e1f5..367dfccb71 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -146,7 +146,7 @@ HINT HINT WARNING - WARNING + HINT WARNING WARNING WARNING From 144144c40ce3954126c493dbc7e220eab620eed2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 25 Dec 2022 20:44:13 -0800 Subject: [PATCH 066/138] Revert removing redundant font parameter --- osu.Game/Graphics/OsuFont.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 12e82469e8..038ea0f5d7 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -21,7 +21,7 @@ namespace osu.Game.Graphics public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Bold); - public static FontUsage Torus => GetFont(weight: FontWeight.Regular); + public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular); public static FontUsage TorusAlternate => GetFont(Typeface.TorusAlternate, weight: FontWeight.Regular); From 5dd03c6c60553d2d73455c89095a4839224eac3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Dec 2022 21:53:52 +0800 Subject: [PATCH 067/138] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index e934b2da6a..c6cf7812d1 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + From fbff5d8d69892b255c78f47b32cdd71311bbaaa1 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 26 Dec 2022 16:16:52 +0100 Subject: [PATCH 068/138] Remove obsoleted "ForDifficultyRating" method --- osu.Game/Graphics/OsuColour.cs | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 91161d5c71..c5659aaf57 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -22,38 +22,8 @@ namespace osu.Game.Graphics public static Color4 Gray(byte amt) => new Color4(amt, amt, amt, 255); /// - /// Retrieves the colour for a . + /// Retrieves the colour for a given point in the star range. /// - /// - /// Sourced from the @diff-{rating} variables in https://github.com/ppy/osu-web/blob/71fbab8936d79a7929d13854f5e854b4f383b236/resources/assets/less/variables.less. - /// - public Color4 ForDifficultyRating(DifficultyRating difficulty, bool useLighterColour = false) - { - switch (difficulty) - { - case DifficultyRating.Easy: - return Color4Extensions.FromHex("4ebfff"); - - case DifficultyRating.Normal: - return Color4Extensions.FromHex("66ff91"); - - case DifficultyRating.Hard: - return Color4Extensions.FromHex("f7e85d"); - - case DifficultyRating.Insane: - return Color4Extensions.FromHex("ff7e68"); - - case DifficultyRating.Expert: - return Color4Extensions.FromHex("fe3c71"); - - case DifficultyRating.ExpertPlus: - return Color4Extensions.FromHex("6662dd"); - - default: - throw new ArgumentOutOfRangeException(nameof(difficulty)); - } - } - public Color4 ForStarDifficulty(double starDifficulty) => ColourUtils.SampleFromLinearGradient(new[] { (0.1f, Color4Extensions.FromHex("aaaaaa")), From c7ca4bbba5a9fc9a6bc12efedb66cbc61531c416 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Mon, 26 Dec 2022 20:36:39 +0100 Subject: [PATCH 069/138] Use generic Enum methods --- .../Skinning/Legacy/LegacyCatcherNew.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs | 2 +- osu.Game.Tournament/IPC/FileBasedIPC.cs | 2 +- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 ++-- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 8 ++++---- osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs | 2 +- .../Online/API/Requests/Responses/APIRecentActivity.cs | 4 ++-- osu.Game/Online/API/Requests/Responses/APIUser.cs | 2 +- osu.Game/OsuGame.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs | 3 +-- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Rulesets/Scoring/HitResult.cs | 2 +- osu.Game/Screens/Edit/Setup/DesignSection.cs | 2 +- osu.Game/Screens/Utility/LatencyCertifierScreen.cs | 2 +- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 2 +- osu.Game/Skinning/Skin.cs | 2 +- 20 files changed, 25 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs index b36d7f11cb..ab753d9c86 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy [BackgroundDependencyLoader] private void load(ISkinSource skin) { - foreach (var state in Enum.GetValues(typeof(CatcherAnimationState)).Cast()) + foreach (var state in Enum.GetValues()) { AddInternal(drawables[state] = getDrawableFor(state).With(d => { diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 122330d09b..ed02284a4b 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.UI HitPolicy = new StartTimeOrderedHitPolicy(); var hitWindows = new OsuHitWindows(); - foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) + foreach (var result in Enum.GetValues().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgementLoaded)); AddRangeInternal(poolDictionary.Values); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 9493de624a..9f9debe7d7 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Taiko.UI var hitWindows = new TaikoHitWindows(); - foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => hitWindows.IsHitResultAllowed(r))) + foreach (var result in Enum.GetValues().Where(r => hitWindows.IsHitResultAllowed(r))) { judgementPools.Add(result, new DrawablePool(15)); explosionPools.Add(result, new HitExplosionPool(result)); diff --git a/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs b/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs index b8a3828a64..ca5240a39d 100644 --- a/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs +++ b/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs @@ -60,6 +60,6 @@ namespace osu.Game.Tests.Mods /// This local helper is used rather than , because the aforementioned method flattens multi mods. /// > private static IEnumerable getMultiMods(Ruleset ruleset) - => Enum.GetValues(typeof(ModType)).Cast().SelectMany(ruleset.GetModsFor).OfType(); + => Enum.GetValues().SelectMany(ruleset.GetModsFor).OfType(); } } diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 2d47560947..7babb3ea5a 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -127,7 +127,7 @@ namespace osu.Game.Tournament.IPC using (var stream = IPCStorage.GetStream(file_ipc_state_filename)) using (var sr = new StreamReader(stream)) { - State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine().AsNonNull()); + State.Value = Enum.Parse(sr.ReadLine().AsNonNull()); } } catch (Exception) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index eb5c9a879a..c9d897ca11 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tournament.Screens.Editors { var countries = new List(); - foreach (var country in Enum.GetValues(typeof(CountryCode)).Cast().Skip(1)) + foreach (var country in Enum.GetValues().Skip(1)) { countries.Add(new TournamentTeam { diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 5f0a2a0824..e865fe7575 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -160,7 +160,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"SampleSet": - defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); + defaultSampleBank = Enum.Parse(pair.Value); break; case @"SampleVolume": @@ -218,7 +218,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"Countdown": - beatmap.BeatmapInfo.Countdown = (CountdownType)Enum.Parse(typeof(CountdownType), pair.Value); + beatmap.BeatmapInfo.Countdown = Enum.Parse(tpair.Value); break; case @"CountdownOffset": diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 2b4f377ab6..491fee642b 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -301,11 +301,11 @@ namespace osu.Game.Beatmaps.Formats } } - private string parseLayer(string value) => Enum.Parse(typeof(LegacyStoryLayer), value).ToString(); + private string parseLayer(string value) => Enum.Parse(value).ToString(); private Anchor parseOrigin(string value) { - var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value); + var origin = Enum.Parse(value); switch (origin) { @@ -343,8 +343,8 @@ namespace osu.Game.Beatmaps.Formats private AnimationLoopType parseAnimationLoopType(string value) { - var parsed = (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), value); - return Enum.IsDefined(typeof(AnimationLoopType), parsed) ? parsed : AnimationLoopType.LoopForever; + var parsed = Enum.Parse(value); + return Enum.IsDefined(parsed) ? parsed : AnimationLoopType.LoopForever; } private void handleVariables(string line) diff --git a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs index 9ef58f4c49..dc089e3410 100644 --- a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs @@ -12,7 +12,7 @@ namespace osu.Game.Graphics.UserInterface { public OsuEnumDropdown() { - Items = (T[])Enum.GetValues(typeof(T)); + Items = Enum.GetValues(); } } } diff --git a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs index 2def18926f..c6a8a85407 100644 --- a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs +++ b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs @@ -21,7 +21,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty] private string type { - set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.ToPascalCase()); + set => Type = Enum.Parse(value.ToPascalCase()); } public RecentActivityType Type; @@ -29,7 +29,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty] private string scoreRank { - set => ScoreRank = (ScoreRank)Enum.Parse(typeof(ScoreRank), value); + set => ScoreRank = Enum.Parse(value); } public ScoreRank ScoreRank; diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 2b6193f661..37a1586e49 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -185,7 +185,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"playstyle")] private string[] playStyle { - set => PlayStyles = value?.Select(str => Enum.Parse(typeof(APIPlayStyle), str, true)).Cast().ToArray(); + set => PlayStyles = value?.Select(str => Enum.Parse(str, true)).Cast().ToArray(); } public APIPlayStyle[] PlayStyles; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 983277135d..a81aa38911 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -726,7 +726,7 @@ namespace osu.Game { base.LoadComplete(); - var languages = Enum.GetValues(typeof(Language)).OfType(); + var languages = Enum.GetValues(); var mappings = languages.Select(language => { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 36fd5a4177..36e248c1f2 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -607,7 +607,7 @@ namespace osu.Game try { - foreach (ModType type in Enum.GetValues(typeof(ModType))) + foreach (ModType type in Enum.GetValues()) { dict[type] = instance.GetModsFor(type) // Rulesets should never return null mods, but let's be defensive just in case. diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index 4af40e5ad6..b8d802ad4b 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -79,8 +79,7 @@ namespace osu.Game.Overlays.FirstRunSetup Direction = FillDirection.Full; Spacing = new Vector2(5); - ChildrenEnumerable = Enum.GetValues(typeof(Language)) - .Cast() + ChildrenEnumerable = Enum.GetValues() .Select(l => new LanguageButton(l) { Action = () => frameworkLocale.Value = l.ToCultureCode() diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index a73151362b..fcf7a78090 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets /// This comes with considerable allocation overhead. If only accessing for reference purposes (ie. not changing bindables / settings) /// use instead. /// - public IEnumerable CreateAllMods() => Enum.GetValues(typeof(ModType)).Cast() + public IEnumerable CreateAllMods() => Enum.GetValues() // Confine all mods of each mod type into a single IEnumerable .SelectMany(GetModsFor) // Filter out all null mods diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 96e13e5861..83ed98768c 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.Scoring /// /// An array of all scorable s. /// - public static readonly HitResult[] ALL_TYPES = ((HitResult[])Enum.GetValues(typeof(HitResult))).Except(new[] { HitResult.LegacyComboIncrease }).ToArray(); + public static readonly HitResult[] ALL_TYPES = Enum.GetValues().Except(new[] { HitResult.LegacyComboIncrease }).ToArray(); /// /// Whether a is valid within a given range. diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 3428366510..fd70b0c142 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit.Setup { Label = EditorSetupStrings.CountdownSpeed, Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None ? Beatmap.BeatmapInfo.Countdown : CountdownType.Normal }, - Items = Enum.GetValues(typeof(CountdownType)).Cast().Where(type => type != CountdownType.None) + Items = Enum.GetValues().Where(type => type != CountdownType.None) }, CountdownOffset = new LabelledNumberBox { diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 212cebeaf5..5c8e448931 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -237,7 +237,7 @@ namespace osu.Game.Screens.Utility switch (e.Key) { case Key.Space: - int availableModes = Enum.GetValues(typeof(LatencyVisualMode)).Length; + int availableModes = Enum.GetValues().Length; VisualMode.Value = (LatencyVisualMode)(((int)VisualMode.Value + 1) % availableModes); return true; diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 0a5f0d22cb..8361619574 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -115,7 +115,7 @@ namespace osu.Game.Skinning.Components .Cast() .ToArray(); - foreach (var type in Enum.GetValues(typeof(BeatmapAttribute)).Cast()) + foreach (var type in Enum.GetValues()) { numberedTemplate = numberedTemplate.Replace($"{{{{{type}}}}}", $"{{{1 + (int)type}}}"); } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 25d1dc903c..19e8bc7092 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -97,7 +97,7 @@ namespace osu.Game.Skinning Configuration = new SkinConfiguration(); // skininfo files may be null for default skin. - foreach (GlobalSkinComponentLookup.LookupType skinnableTarget in Enum.GetValues(typeof(GlobalSkinComponentLookup.LookupType))) + foreach (GlobalSkinComponentLookup.LookupType skinnableTarget in Enum.GetValues()) { string filename = $"{skinnableTarget}.json"; From fcbb21c75eb35a1313f9e998bed1d335f09a40a2 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Mon, 26 Dec 2022 20:38:35 +0100 Subject: [PATCH 070/138] Fix typo --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index e865fe7575..9c710b690e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -218,7 +218,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"Countdown": - beatmap.BeatmapInfo.Countdown = Enum.Parse(tpair.Value); + beatmap.BeatmapInfo.Countdown = Enum.Parse(pair.Value); break; case @"CountdownOffset": From 335cb0205fb0729afb0c54744dd4992f7510bae7 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Mon, 26 Dec 2022 22:50:36 +0100 Subject: [PATCH 071/138] Remove now unnecessary using --- osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs index ab753d9c86..f6b2c52498 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; From cb2b0d41788e03e0cad4830ca6d1c854a300d0e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Dec 2022 23:12:53 +0100 Subject: [PATCH 072/138] Remove redundant type specs --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 +- osu.Game/Online/API/Requests/Responses/APIUser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 491fee642b..44dbb3cc9f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -344,7 +344,7 @@ namespace osu.Game.Beatmaps.Formats private AnimationLoopType parseAnimationLoopType(string value) { var parsed = Enum.Parse(value); - return Enum.IsDefined(parsed) ? parsed : AnimationLoopType.LoopForever; + return Enum.IsDefined(parsed) ? parsed : AnimationLoopType.LoopForever; } private void handleVariables(string line) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 37a1586e49..9cb0c0704d 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -185,7 +185,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"playstyle")] private string[] playStyle { - set => PlayStyles = value?.Select(str => Enum.Parse(str, true)).Cast().ToArray(); + set => PlayStyles = value?.Select(str => Enum.Parse(str, true)).ToArray(); } public APIPlayStyle[] PlayStyles; From 01cf96e240d8a9d1ea99ec072797bdf10d0f6292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Dec 2022 23:25:54 +0100 Subject: [PATCH 073/138] Only show global rankings on results screen when progressing from gameplay --- osu.Game/Screens/Play/Player.cs | 5 +++- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 23 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4306d13ac2..05133fba35 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1159,7 +1159,10 @@ namespace osu.Game.Screens.Play /// /// The to be displayed in the results screen. /// The . - protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, true); + protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, true) + { + ShowUserStatistics = true + }; private void fadeOut(bool instant = false) { diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 6d4feeb0db..110e813e04 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -20,6 +20,12 @@ namespace osu.Game.Screens.Ranking { public partial class SoloResultsScreen : ResultsScreen { + /// + /// Whether the user's personal statistics should be shown on the extended statistics panel + /// after clicking the score panel associated with the being presented. + /// + public bool ShowUserStatistics { get; init; } + private GetScoresRequest getScoreRequest; [Resolved] @@ -39,13 +45,22 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); + if (ShowUserStatistics) + soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); } - protected override StatisticsPanel CreateStatisticsPanel() => new SoloStatisticsPanel(Score) + protected override StatisticsPanel CreateStatisticsPanel() { - StatisticsUpdate = { BindTarget = statisticsUpdate } - }; + if (ShowUserStatistics) + { + return new SoloStatisticsPanel(Score) + { + StatisticsUpdate = { BindTarget = statisticsUpdate } + }; + } + + return base.CreateStatisticsPanel(); + } protected override APIRequest FetchScores(Action> scoresCallback) { From 777ffcf805f07954377832a869472afee8c09a0a Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 26 Dec 2022 20:45:32 -0800 Subject: [PATCH 074/138] Highlight "open" option on external link button context menu --- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index efbbaaca85..4eccb37613 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -82,7 +82,7 @@ namespace osu.Game.Graphics.UserInterface if (Link != null) { - items.Add(new OsuMenuItem("Open", MenuItemType.Standard, () => host.OpenUrlExternally(Link))); + items.Add(new OsuMenuItem("Open", MenuItemType.Highlighted, () => host.OpenUrlExternally(Link))); items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, copyUrl)); } From 182f36c434bd2b60895403cf07a1adde88912067 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Tue, 27 Dec 2022 09:41:58 +0100 Subject: [PATCH 075/138] Use StringSplitOptions.TrimEntries for string.Split() when possible --- .../Screens/Drawings/Components/StorageBackedTeamList.cs | 8 ++++---- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 8 +------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs index c230607343..74afb42c1a 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components continue; // ReSharper disable once PossibleNullReferenceException - string[] split = line.Split(':'); + string[] split = line.Split(':', StringSplitOptions.TrimEntries); if (split.Length < 2) { @@ -55,9 +55,9 @@ namespace osu.Game.Tournament.Screens.Drawings.Components teams.Add(new TournamentTeam { - FullName = { Value = split[1].Trim(), }, - Acronym = { Value = split.Length >= 3 ? split[2].Trim() : null, }, - FlagName = { Value = split[0].Trim() } + FullName = { Value = split[1], }, + Acronym = { Value = split.Length >= 3 ? split[2] : null, }, + FlagName = { Value = split[0] } }); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index a4e15f790a..704756e3dd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -132,13 +132,7 @@ namespace osu.Game.Beatmaps.Formats protected KeyValuePair SplitKeyVal(string line, char separator = ':', bool shouldTrim = true) { - string[] split = line.Split(separator, 2); - - if (shouldTrim) - { - for (int i = 0; i < split.Length; i++) - split[i] = split[i].Trim(); - } + string[] split = line.Split(separator, 2, shouldTrim ? StringSplitOptions.TrimEntries : StringSplitOptions.None); return new KeyValuePair ( From b3e44f20bca94f78599500027eaaa7ce164f8e65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Dec 2022 21:53:41 +0800 Subject: [PATCH 076/138] Use new lazer API endpoint This is a temporary change to target the new experimental/next deploy. The main change that should result from this is having the user profile show the pp^next values from the new domain. --- osu.Game/Online/ProductionEndpointConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/ProductionEndpointConfiguration.cs b/osu.Game/Online/ProductionEndpointConfiguration.cs index 316452280d..3a74d24b58 100644 --- a/osu.Game/Online/ProductionEndpointConfiguration.cs +++ b/osu.Game/Online/ProductionEndpointConfiguration.cs @@ -9,7 +9,7 @@ namespace osu.Game.Online { public ProductionEndpointConfiguration() { - WebsiteRootUrl = APIEndpointUrl = @"https://osu.ppy.sh"; + WebsiteRootUrl = APIEndpointUrl = @"https://lazer.ppy.sh"; APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; APIClientID = "5"; SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator"; From 61029b126d03f00bea9b7ecfa3862452fcf7c7e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Dec 2022 17:50:30 +0800 Subject: [PATCH 077/138] Add link to hard link explanation wiki page --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 8b85bb49a5..0f8a78453d 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -22,6 +22,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; +using osu.Game.Online.Chat; using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections.Maintenance; using osu.Game.Screens.Edit.Setup; @@ -127,7 +128,9 @@ namespace osu.Game.Overlays.FirstRunSetup if (available) { copyInformation.Text = - "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation."; + "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation. "; + + copyInformation.AddLink("Learn more about how \"hard links\" work", LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links"); } else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; From 1a4489edb28c98907fda40551bd7f42f66174d2e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 27 Dec 2022 14:55:51 +0300 Subject: [PATCH 078/138] Move version pinning of system packages to `osu.Game` --- osu.Game/osu.Game.csproj | 5 +++++ osu.iOS.props | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index da14ed123f..cce3e42be4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -43,5 +43,10 @@ + + + + + diff --git a/osu.iOS.props b/osu.iOS.props index 9b9abfc37b..6201022da1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,10 +17,5 @@ - - - - - From e2703bba188b606760c74023f4cf7d62fdd8792a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Dec 2022 19:48:18 +0100 Subject: [PATCH 079/138] Fix invalid data in test scene --- .../Visual/Ranking/TestSceneOverallRanking.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs index 2edc577a95..355a572f95 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Ranking new UserStatistics { GlobalRank = 12_345, - Accuracy = 0.9899, + Accuracy = 98.99, MaxCombo = 2_322, RankedScore = 23_123_543_456, TotalScore = 123_123_543_456, @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Ranking new UserStatistics { GlobalRank = 1_234, - Accuracy = 0.9907, + Accuracy = 99.07, MaxCombo = 2_352, RankedScore = 23_124_231_435, TotalScore = 123_124_231_435, @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Ranking new UserStatistics { GlobalRank = 1_234, - Accuracy = 0.9907, + Accuracy = 99.07, MaxCombo = 2_352, RankedScore = 23_124_231_435, TotalScore = 123_124_231_435, @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Ranking new UserStatistics { GlobalRank = 12_345, - Accuracy = 0.9899, + Accuracy = 98.99, MaxCombo = 2_322, RankedScore = 23_123_543_456, TotalScore = 123_123_543_456, @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Ranking var statistics = new UserStatistics { GlobalRank = 12_345, - Accuracy = 0.9899, + Accuracy = 98.99, MaxCombo = 2_322, RankedScore = 23_123_543_456, TotalScore = 123_123_543_456, @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual.Ranking var statistics = new UserStatistics { GlobalRank = null, - Accuracy = 0.9899, + Accuracy = 98.99, MaxCombo = 2_322, RankedScore = 23_123_543_456, TotalScore = 123_123_543_456, From e90619244da7ff3151c329193c4e80a4f602daa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Dec 2022 19:51:51 +0100 Subject: [PATCH 080/138] Fix incorrect accuracy display on overall ranking view --- osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs index 0f5dd9074a..0fd666e9d0 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs @@ -16,11 +16,11 @@ namespace osu.Game.Screens.Ranking.Statistics.User protected override LocalisableString Label => UsersStrings.ShowStatsHitAccuracy; - protected override LocalisableString FormatCurrentValue(double current) => current.FormatAccuracy(); + protected override LocalisableString FormatCurrentValue(double current) => (current / 100).FormatAccuracy(); protected override int CalculateDifference(double previous, double current, out LocalisableString formattedDifference) { - double difference = current - previous; + double difference = (current - previous) / 100; if (difference < 0) formattedDifference = difference.FormatAccuracy(); From 0d78bc224826fc70deaf961ba0c4b5a997cb8e71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Dec 2022 06:42:32 +0800 Subject: [PATCH 081/138] Fix `osu.ppy.sh` links no longer opening in-game Addresses https://github.com/ppy/osu/discussions/21838. --- osu.Game/Online/ProductionEndpointConfiguration.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/ProductionEndpointConfiguration.cs b/osu.Game/Online/ProductionEndpointConfiguration.cs index 3a74d24b58..003ec50afd 100644 --- a/osu.Game/Online/ProductionEndpointConfiguration.cs +++ b/osu.Game/Online/ProductionEndpointConfiguration.cs @@ -9,7 +9,8 @@ namespace osu.Game.Online { public ProductionEndpointConfiguration() { - WebsiteRootUrl = APIEndpointUrl = @"https://lazer.ppy.sh"; + WebsiteRootUrl = @"https://osu.ppy.sh"; + APIEndpointUrl = @"https://lazer.ppy.sh"; APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; APIClientID = "5"; SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator"; From e9d32fca18151036b7d2ba95fc7bae8f771671d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 07:28:18 +0100 Subject: [PATCH 082/138] Fix various failures in initial statistics fetch - If the local user is restricted, then attempting to fetch their data from the `/users` endpoint would result in an empty response. - Even if the user was successfully fetched, their `RulesetsStatistics` may not be populated (and instead be `null`). Curiously this was not picked up by static analysis until the first issue was fixed. Closes #21839. --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 33344044b9..b2f371fd74 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -75,15 +75,27 @@ namespace osu.Game.Online.Solo return; var userRequest = new GetUsersRequest(new[] { localUser.OnlineID }); - userRequest.Success += response => Schedule(() => - { - latestStatistics = new Dictionary(); - foreach (var rulesetStats in response.Users.Single().RulesetsStatistics) - latestStatistics.Add(rulesetStats.Key, rulesetStats.Value); - }); + userRequest.Success += initialiseUserStatistics; api.Queue(userRequest); }); + private void initialiseUserStatistics(GetUsersResponse response) => Schedule(() => + { + var user = response.Users.SingleOrDefault(); + + // possible if the user is restricted or similar. + if (user == null) + return; + + latestStatistics = new Dictionary(); + + if (user.RulesetsStatistics != null) + { + foreach (var rulesetStats in user.RulesetsStatistics) + latestStatistics.Add(rulesetStats.Key, rulesetStats.Value); + } + }); + private void userScoreProcessed(int userId, long scoreId) { if (userId != api.LocalUser.Value?.OnlineID) From a0a26b1e8c257b40dc66ad62be754afcd8a7edda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 07:37:52 +0100 Subject: [PATCH 083/138] Ignore statistics update subscriptions with invalid score ID If score submission fails, the score will not receive a correct online ID from web, but will still be passed on to the solo statistics watcher on the results screen. This could lead to the watcher subscribing to changes with score ID equal to the default of -1. If this happened more than once, that would cause a crash due to duplicate keys in the `callbacks` dictionary. Closes #21837. --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index b2f371fd74..235abde068 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -51,7 +51,7 @@ namespace osu.Game.Online.Solo if (!api.IsLoggedIn) return; - if (!score.Ruleset.IsLegacyRuleset()) + if (!score.Ruleset.IsLegacyRuleset() || score.OnlineID <= 0) return; var callback = new StatisticsUpdateCallback(score, onUpdateReady); From 04f9a354c3fb6e695e4cad4bfddd31b5ac8c03e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 07:49:09 +0100 Subject: [PATCH 084/138] Convert `SoloResultsScreen` to NRT --- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 110e813e04..94d333c2da 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -26,15 +24,15 @@ namespace osu.Game.Screens.Ranking /// public bool ShowUserStatistics { get; init; } - private GetScoresRequest getScoreRequest; + private GetScoresRequest? getScoreRequest; [Resolved] - private RulesetStore rulesets { get; set; } + private RulesetStore rulesets { get; set; } = null!; [Resolved] - private SoloStatisticsWatcher soloStatisticsWatcher { get; set; } + private SoloStatisticsWatcher soloStatisticsWatcher { get; set; } = null!; - private readonly Bindable statisticsUpdate = new Bindable(); + private readonly Bindable statisticsUpdate = new Bindable(); public SoloResultsScreen(ScoreInfo score, bool allowRetry) : base(score, allowRetry) @@ -62,7 +60,7 @@ namespace osu.Game.Screens.Ranking return base.CreateStatisticsPanel(); } - protected override APIRequest FetchScores(Action> scoresCallback) + protected override APIRequest? FetchScores(Action>? scoresCallback) { if (Score.BeatmapInfo.OnlineID <= 0 || Score.BeatmapInfo.Status <= BeatmapOnlineStatus.Pending) return null; From 3c0b8af8f1dbf19dbd9496c85d3555354fff7e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 07:50:10 +0100 Subject: [PATCH 085/138] Allow unsubscribing from solo statistics updates This is more of a safety item. To avoid potential duplicate key in dictionary errors (and also avoid being slightly memory-leaky), allow `SoloStatisticsWatcher` consumers to dispose of the subscriptions they take out. --- .../Online/TestSceneSoloStatisticsWatcher.cs | 24 ++++++++++++- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 36 +++++++++++-------- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 4 ++- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index b1badc6282..e62e53bd02 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -35,6 +35,8 @@ namespace osu.Game.Tests.Visual.Online private Action? handleGetUsersRequest; private Action? handleGetUserRequest; + private IDisposable? subscription; + private readonly Dictionary<(int userId, string rulesetName), UserStatistics> serverSideStatistics = new Dictionary<(int userId, string rulesetName), UserStatistics>(); [SetUpSteps] @@ -246,6 +248,26 @@ namespace osu.Game.Tests.Visual.Online AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(6_000_000)); } + [Test] + public void TestStatisticsUpdateNotFiredAfterSubscriptionDisposal() + { + int userId = getUserId(); + setUpUser(userId); + + long scoreId = getScoreId(); + var ruleset = new OsuRuleset().RulesetInfo; + + SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); + AddStep("unsubscribe", () => subscription!.Dispose()); + + feignScoreProcessing(userId, ruleset, 5_000_000); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); + AddWaitStep("wait a bit", 5); + AddAssert("update not received", () => update == null); + } + private int nextUserId = 2000; private long nextScoreId = 50000; @@ -266,7 +288,7 @@ namespace osu.Game.Tests.Visual.Online } private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action onUpdateReady) => - AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + AddStep("register for updates", () => subscription = watcher.RegisterForStatisticsUpdateAfter( new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) { Ruleset = rulesetInfo, diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 235abde068..46449fea73 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -46,24 +46,30 @@ namespace osu.Game.Online.Solo /// /// The score to listen for the statistics update for. /// The callback to be invoked once the statistics update has been prepared. - public void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady) => Schedule(() => + /// An representing the subscription. Disposing it is equivalent to unsubscribing from future notifications. + public IDisposable RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady) { - if (!api.IsLoggedIn) - return; - - if (!score.Ruleset.IsLegacyRuleset() || score.OnlineID <= 0) - return; - - var callback = new StatisticsUpdateCallback(score, onUpdateReady); - - if (lastProcessedScoreId == score.OnlineID) + Schedule(() => { - requestStatisticsUpdate(api.LocalUser.Value.Id, callback); - return; - } + if (!api.IsLoggedIn) + return; - callbacks.Add(score.OnlineID, callback); - }); + if (!score.Ruleset.IsLegacyRuleset() || score.OnlineID <= 0) + return; + + var callback = new StatisticsUpdateCallback(score, onUpdateReady); + + if (lastProcessedScoreId == score.OnlineID) + { + requestStatisticsUpdate(api.LocalUser.Value.Id, callback); + return; + } + + callbacks.Add(score.OnlineID, callback); + }); + + return new InvokeOnDisposal(() => Schedule(() => callbacks.Remove(score.OnlineID))); + } private void onUserChanged(APIUser? localUser) => Schedule(() => { diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 94d333c2da..c8920a734d 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -32,6 +32,7 @@ namespace osu.Game.Screens.Ranking [Resolved] private SoloStatisticsWatcher soloStatisticsWatcher { get; set; } = null!; + private IDisposable? statisticsSubscription; private readonly Bindable statisticsUpdate = new Bindable(); public SoloResultsScreen(ScoreInfo score, bool allowRetry) @@ -44,7 +45,7 @@ namespace osu.Game.Screens.Ranking base.LoadComplete(); if (ShowUserStatistics) - soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); + statisticsSubscription = soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); } protected override StatisticsPanel CreateStatisticsPanel() @@ -75,6 +76,7 @@ namespace osu.Game.Screens.Ranking base.Dispose(isDisposing); getScoreRequest?.Cancel(); + statisticsSubscription?.Dispose(); } } } From 76367444cbebff0d893ae941df3e0179d9d9c3d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 10:36:54 +0100 Subject: [PATCH 086/138] Adjust Android package versioning to .NET 6 With .NET 6, the way Xamarin package versioning works has changed. - The `ApplicationVersion` MSBuild property aims to replace `android:versionCode` in the manifest. - The `ApplicationDisplayVersion` MSBuild property aims to replace `android:versionName` in the manifest. More about this can be read in Xamarin docs: https://github.com/xamarin/xamarin-android/blob/ec712da8c1ce03f71578e08cafb6a767cdb90cd5/Documentation/guides/OneDotNetSingleProject.md To this end: - Manual `version{Code,Name}` specs are removed from `AndroidManifest.xml`, as they were preventing MSBuild properties from functioning properly. - `Version` now defaults to 0.0.0, so that local builds don't appear like they were deployed (see `OsuGameBase.IsDeployedBuild`). - `ApplicationDisplayVersion` now defaults to `Version`. This addresses the Android portion of #21498. - `ApplicationVersion` can now be specified by command line, but still needs to be supplied manually for version detection to work correctly. See `OsuGameAndroid.AssemblyVersion` for more info. Putting the pieces together, the complete publish command to deploy a new build should look something like so: dotnet publish -f net6.0-android \ -r android-arm64 \ -c Release \ -p:Version=2022.1228.0 \ -p:ApplicationVersion=202212280 --- osu.Android/AndroidManifest.xml | 2 +- osu.Android/osu.Android.csproj | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Android/AndroidManifest.xml b/osu.Android/AndroidManifest.xml index be326be5eb..bc2f49b1a9 100644 --- a/osu.Android/AndroidManifest.xml +++ b/osu.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index de53e5dd59..1507bfaa29 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -8,6 +8,9 @@ true false + 0.0.0 + 1 + $(Version) From b4c5e18da0347872e068086d6954d3321721ad1a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 28 Dec 2022 13:23:46 +0300 Subject: [PATCH 087/138] Add keywords to ease search of "first object visibility" setting --- osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs index 69e24bc616..f6b3c12487 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs @@ -27,6 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = GameplaySettingsStrings.IncreaseFirstObjectVisibility, Current = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility), + Keywords = new[] { @"approach", @"circle", @"hidden" }, }, }; } From b2aa2e16029d984109049fce98c58466be867709 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 13:23:07 +0100 Subject: [PATCH 088/138] Add hardlink support for Linux --- osu.Game/Database/RealmFileStore.cs | 16 +++++++++++---- osu.Game/IO/HardLinkHelper.cs | 20 ++++++++++++++----- .../FirstRunSetup/ScreenImportFromStable.cs | 2 +- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 04b503b808..49102d81e5 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -63,11 +63,19 @@ namespace osu.Game.Database private void copyToStore(RealmFile file, Stream data, bool preferHardLinks) { - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows && data is FileStream fs && preferHardLinks) + // attempt to do a fast hard link rather than copy. + if (data is FileStream fs && preferHardLinks) { - // attempt to do a fast hard link rather than copy. - if (HardLinkHelper.CreateHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name, IntPtr.Zero)) - return; + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) + { + if (HardLinkHelper.CreateHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name, IntPtr.Zero)) + return; + } + else if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux) + { + if (HardLinkHelper.link(fs.Name, Storage.GetFullPath(file.GetStoragePath(), true)) == 0) + return; + } } data.Seek(0, SeekOrigin.Begin); diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index 1393bf26fd..8d521bab9f 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -14,9 +14,8 @@ namespace osu.Game.IO { public static bool CheckAvailability(string testDestinationPath, string testSourcePath) { - // We can support other operating systems quite easily in the future. - // Let's handle the most common one for now, though. - if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) + // TODO: Add macOS support for hardlinks. + if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows || RuntimeInfo.OS != RuntimeInfo.Platform.Linux) return false; const string test_filename = "_hard_link_test"; @@ -26,12 +25,20 @@ namespace osu.Game.IO cleanupFiles(); + try { File.WriteAllText(testSourcePath, string.Empty); - // Test availability by creating an arbitrary hard link between the source and destination paths. - return CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); + + bool isHardLinkAvailable = false; + + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) + isHardLinkAvailable = CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); + else if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux) + isHardLinkAvailable = link(testSourcePath, testDestinationPath) == 0; + + return isHardLinkAvailable; } catch { @@ -70,6 +77,9 @@ namespace osu.Game.IO return result; } + [DllImport("libc", SetLastError = true)] + public static extern int link(string oldpath, string newpath); + [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 0f8a78453d..2603a6c6c4 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.AddLink("Learn more about how \"hard links\" work", LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links"); } - else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) + else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows || RuntimeInfo.OS != RuntimeInfo.Platform.Linux) copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; else { From 16165b1f67fc51ea01b906b020dc195788f99a2b Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 13:58:52 +0100 Subject: [PATCH 089/138] Remove blank line --- osu.Game/IO/HardLinkHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index 8d521bab9f..b3eb528bbb 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -25,7 +25,6 @@ namespace osu.Game.IO cleanupFiles(); - try { File.WriteAllText(testSourcePath, string.Empty); From 5c5e84f931a45b5c35261d2e38aabc857730bc47 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 14:06:50 +0100 Subject: [PATCH 090/138] Fix formatiing --- osu.Game/IO/HardLinkHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index b3eb528bbb..d059e8b710 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -76,7 +76,7 @@ namespace osu.Game.IO return result; } - [DllImport("libc", SetLastError = true)] + [DllImport("libc", SetLastError = true)] public static extern int link(string oldpath, string newpath); [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] From f32564652b3d07224c10cce449ba7dd28b7fecf4 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 14:33:38 +0100 Subject: [PATCH 091/138] Mention the filesystem should be NTFS on Windows --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 2603a6c6c4..6e9b724774 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -136,8 +136,10 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; else { + string mentionNTFS = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? " (and the file system is NTFS)." : "."; + copyInformation.Text = - "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS). "; + $"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install{mentionNTFS}"; copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () => { game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())); From 53bca947d13fe267260e3767c67cd6e30efd2154 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 14:34:27 +0100 Subject: [PATCH 092/138] Move duplicated code into its own method --- osu.Game/Database/RealmFileStore.cs | 15 ++------------- osu.Game/IO/HardLinkHelper.cs | 23 ++++++++++++++--------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 49102d81e5..e5ace5b346 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -64,19 +64,8 @@ namespace osu.Game.Database private void copyToStore(RealmFile file, Stream data, bool preferHardLinks) { // attempt to do a fast hard link rather than copy. - if (data is FileStream fs && preferHardLinks) - { - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) - { - if (HardLinkHelper.CreateHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name, IntPtr.Zero)) - return; - } - else if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux) - { - if (HardLinkHelper.link(fs.Name, Storage.GetFullPath(file.GetStoragePath(), true)) == 0) - return; - } - } + if (data is FileStream fs && preferHardLinks && HardLinkHelper.AttemptHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name)) + return; data.Seek(0, SeekOrigin.Begin); diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index d059e8b710..c8fc6d9f49 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -28,16 +28,9 @@ namespace osu.Game.IO try { File.WriteAllText(testSourcePath, string.Empty); + // Test availability by creating an arbitrary hard link between the source and destination paths. - - bool isHardLinkAvailable = false; - - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) - isHardLinkAvailable = CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); - else if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux) - isHardLinkAvailable = link(testSourcePath, testDestinationPath) == 0; - - return isHardLinkAvailable; + return AttemptHardLink(testDestinationPath, testSourcePath); } catch { @@ -61,6 +54,18 @@ namespace osu.Game.IO } } + public static bool AttemptHardLink(string testDestinationPath, string testSourcePath) + { + bool isHardLinkAvailable = false; + + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) + isHardLinkAvailable = CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); + else if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux) + isHardLinkAvailable = link(testSourcePath, testDestinationPath) == 0; + + return isHardLinkAvailable; + } + // For future use (to detect if a file is a hard link with other references existing on disk). public static int GetFileLinkCount(string filePath) { From c6da7248bacc02b4879c329d68238d8865b46cd6 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 14:40:32 +0100 Subject: [PATCH 093/138] Remove unnecessary directive --- osu.Game/Database/RealmFileStore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index e5ace5b346..df276adbaf 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Linq; -using osu.Framework; using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Logging; From d63be3ff17b8eaee6e39d757db32c8f124e4e3a6 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 15:02:44 +0100 Subject: [PATCH 094/138] Change name of variable --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 6e9b724774..74c1bf955c 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -136,10 +136,10 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; else { - string mentionNTFS = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? " (and the file system is NTFS)." : "."; + string mentionNtfs = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? " (and the file system is NTFS)." : "."; copyInformation.Text = - $"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install{mentionNTFS}"; + $"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install{mentionNtfs}"; copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () => { game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())); From 2c346eae0d827abb91a05a3c0300a3c9e02076d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 21:19:28 +0100 Subject: [PATCH 095/138] Revert inlining of hard link creation into condition Just feels bad. Mixing data access with actual underlying logic. --- osu.Game/Database/RealmFileStore.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index df276adbaf..4f429cb20c 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -62,9 +62,12 @@ namespace osu.Game.Database private void copyToStore(RealmFile file, Stream data, bool preferHardLinks) { - // attempt to do a fast hard link rather than copy. - if (data is FileStream fs && preferHardLinks && HardLinkHelper.AttemptHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name)) - return; + if (data is FileStream fs && preferHardLinks) + { + // attempt to do a fast hard link rather than copy. + if (HardLinkHelper.AttemptHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name)) + return; + } data.Seek(0, SeekOrigin.Begin); From cadd487c75c0876ec15388f55577cbac97d2df2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 21:20:49 +0100 Subject: [PATCH 096/138] Use switch statement in `AttemptHardLink()` --- osu.Game/IO/HardLinkHelper.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index c8fc6d9f49..d6ca11a68f 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -56,14 +56,17 @@ namespace osu.Game.IO public static bool AttemptHardLink(string testDestinationPath, string testSourcePath) { - bool isHardLinkAvailable = false; + switch (RuntimeInfo.OS) + { + case RuntimeInfo.Platform.Windows: + return CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) - isHardLinkAvailable = CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); - else if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux) - isHardLinkAvailable = link(testSourcePath, testDestinationPath) == 0; + case RuntimeInfo.Platform.Linux: + return link(testSourcePath, testDestinationPath) == 0; - return isHardLinkAvailable; + default: + return false; + } } // For future use (to detect if a file is a hard link with other references existing on disk). From 04d4b4a6ce2a7cb5aff2bea79e8dd6378f34a833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 21:23:06 +0100 Subject: [PATCH 097/138] Rename and xmldoc hard link creation method --- osu.Game/Database/RealmFileStore.cs | 2 +- osu.Game/IO/HardLinkHelper.cs | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 4f429cb20c..f75d3be725 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -65,7 +65,7 @@ namespace osu.Game.Database if (data is FileStream fs && preferHardLinks) { // attempt to do a fast hard link rather than copy. - if (HardLinkHelper.AttemptHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name)) + if (HardLinkHelper.TryCreateHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name)) return; } diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index d6ca11a68f..9c0a8fbba1 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -30,7 +30,7 @@ namespace osu.Game.IO File.WriteAllText(testSourcePath, string.Empty); // Test availability by creating an arbitrary hard link between the source and destination paths. - return AttemptHardLink(testDestinationPath, testSourcePath); + return TryCreateHardLink(testDestinationPath, testSourcePath); } catch { @@ -54,15 +54,23 @@ namespace osu.Game.IO } } - public static bool AttemptHardLink(string testDestinationPath, string testSourcePath) + /// + /// Attempts to create a hard link from to , + /// using platform-specific native methods. + /// + /// + /// Hard links are only available on Windows and Linux. + /// + /// Whether the hard link was successfully created. + public static bool TryCreateHardLink(string destinationPath, string sourcePath) { switch (RuntimeInfo.OS) { case RuntimeInfo.Platform.Windows: - return CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); + return CreateHardLink(destinationPath, sourcePath, IntPtr.Zero); case RuntimeInfo.Platform.Linux: - return link(testSourcePath, testDestinationPath) == 0; + return link(sourcePath, destinationPath) == 0; default: return false; From d4b3965967ea155e94975409de1a5883c3981523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 21:25:24 +0100 Subject: [PATCH 098/138] Change warning message about file duplication - It was being glued in an ugly way that would have prevented sanely localising it. - Even on Linux, the filesystem (whichever one the user has chosen out of the multitude available) still needs to support hard links for them to have a chance of working. --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 74c1bf955c..8b1b50ed98 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -136,10 +136,9 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; else { - string mentionNtfs = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? " (and the file system is NTFS)." : "."; - - copyInformation.Text = - $"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install{mentionNtfs}"; + copyInformation.Text = RuntimeInfo.OS == RuntimeInfo.Platform.Windows + ? "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS)." + : "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links)."; copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () => { game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())); From 8d79fa93ac06a5913628a0eb2ba847a869fd7e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 21:58:30 +0100 Subject: [PATCH 099/138] Implement `GetFileLinkCount()` for Linux --- osu.Game/IO/HardLinkHelper.cs | 65 +++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index 9c0a8fbba1..03cb043cca 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -81,19 +81,30 @@ namespace osu.Game.IO public static int GetFileLinkCount(string filePath) { int result = 0; - SafeFileHandle handle = CreateFile(filePath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero); - ByHandleFileInformation fileInfo; + switch (RuntimeInfo.OS) + { + case RuntimeInfo.Platform.Windows: + SafeFileHandle handle = CreateFile(filePath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero); - if (GetFileInformationByHandle(handle, out fileInfo)) - result = (int)fileInfo.NumberOfLinks; - CloseHandle(handle); + ByHandleFileInformation fileInfo; + + if (GetFileInformationByHandle(handle, out fileInfo)) + result = (int)fileInfo.NumberOfLinks; + CloseHandle(handle); + break; + + case RuntimeInfo.Platform.Linux: + if (stat(filePath, out var statbuf) == 0) + result = (int)statbuf.st_nlink; + + break; + } return result; } - [DllImport("libc", SetLastError = true)] - public static extern int link(string oldpath, string newpath); + #region Windows native methods [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); @@ -129,5 +140,45 @@ namespace osu.Game.IO public readonly uint FileIndexHigh; public readonly uint FileIndexLow; } + + #endregion + + #region Linux native methods + + [DllImport("libc", SetLastError = true)] + public static extern int link(string oldpath, string newpath); + + [DllImport("libc", SetLastError = true)] + private static extern int stat(string pathname, out struct_stat statbuf); + + // ReSharper disable once InconsistentNaming + // Struct layout is likely non-portable across unices. Tread with caution. + [StructLayout(LayoutKind.Sequential)] + private struct struct_stat + { + public readonly long st_dev; + public readonly long st_ino; + public readonly long st_nlink; + public readonly int st_mode; + public readonly int st_uid; + public readonly int st_gid; + public readonly long st_rdev; + public readonly long st_size; + public readonly long st_blksize; + public readonly long st_blocks; + public readonly timespec st_atim; + public readonly timespec st_mtim; + public readonly timespec st_ctim; + } + + // ReSharper disable once InconsistentNaming + [StructLayout(LayoutKind.Sequential)] + private struct timespec + { + public readonly long tv_sec; + public readonly long tv_nsec; + } + + #endregion } } From 49b0ec9ddb43d0f507ba8155d7bec69ed7876bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 22:18:27 +0100 Subject: [PATCH 100/138] Fix broken condition --- osu.Game/IO/HardLinkHelper.cs | 2 +- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index 03cb043cca..fef084f5f0 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -15,7 +15,7 @@ namespace osu.Game.IO public static bool CheckAvailability(string testDestinationPath, string testSourcePath) { // TODO: Add macOS support for hardlinks. - if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows || RuntimeInfo.OS != RuntimeInfo.Platform.Linux) + if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows && RuntimeInfo.OS != RuntimeInfo.Platform.Linux) return false; const string test_filename = "_hard_link_test"; diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 8b1b50ed98..0e7514a7dc 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.AddLink("Learn more about how \"hard links\" work", LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links"); } - else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows || RuntimeInfo.OS != RuntimeInfo.Platform.Linux) + else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows && RuntimeInfo.OS != RuntimeInfo.Platform.Linux) copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; else { From 10c11e974de9c2533059bcd76b7abf248f497ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 22:19:49 +0100 Subject: [PATCH 101/138] Fix broken spacing --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 0e7514a7dc..2e3ab823f7 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -137,8 +137,8 @@ namespace osu.Game.Overlays.FirstRunSetup else { copyInformation.Text = RuntimeInfo.OS == RuntimeInfo.Platform.Windows - ? "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS)." - : "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links)."; + ? "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS). " + : "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links). "; copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () => { game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())); From 74bc5d46666403f54f9f6e5336c028e00cf1ff03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 22:38:42 +0100 Subject: [PATCH 102/138] Disable naming rule inspection on struct stat definition --- osu.Game/IO/HardLinkHelper.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index fef084f5f0..ebf2d2bd71 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -145,6 +145,8 @@ namespace osu.Game.IO #region Linux native methods +#pragma warning disable IDE1006 // Naming rule violation + [DllImport("libc", SetLastError = true)] public static extern int link(string oldpath, string newpath); @@ -179,6 +181,8 @@ namespace osu.Game.IO public readonly long tv_nsec; } +#pragma warning restore IDE1006 + #endregion } } From ffd9359f4a6c9ce223d889b87b6b936b9b8de333 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 28 Dec 2022 14:50:40 -0800 Subject: [PATCH 103/138] Add tests for chat text box saving / syncing --- .../Visual/Online/TestSceneChatOverlay.cs | 46 +++++++++++++++++++ .../Online/TestSceneStandAloneChatDisplay.cs | 32 +++++++++++-- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 8cc4eabcd7..a8369dd6d9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -530,6 +530,52 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestTextBoxSavePerChannel() + { + var testPMChannel = new Channel(testUser); + + AddStep("show overlay", () => chatOverlay.Show()); + joinTestChannel(0); + joinChannel(testPMChannel); + + AddAssert("listing is visible", () => listingIsVisible); + AddStep("search for 'number 2'", () => chatOverlayTextBox.Text = "number 2"); + AddAssert("'number 2' saved to selector", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == "number 2"); + + AddStep("select normal channel", () => clickDrawable(getChannelListItem(testChannel1))); + AddAssert("text box cleared on normal channel", () => chatOverlayTextBox.Text == string.Empty); + AddAssert("nothing saved on normal channel", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == string.Empty); + AddStep("type '727'", () => chatOverlayTextBox.Text = "727"); + AddAssert("'727' saved to normal channel", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == "727"); + + AddStep("select PM channel", () => clickDrawable(getChannelListItem(testPMChannel))); + AddAssert("text box cleared on PM channel", () => chatOverlayTextBox.Text == string.Empty); + AddAssert("nothing saved on PM channel", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == string.Empty); + AddStep("type 'hello'", () => chatOverlayTextBox.Text = "hello"); + AddAssert("'hello' saved to PM channel", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == "hello"); + + AddStep("select normal channel", () => clickDrawable(getChannelListItem(testChannel1))); + AddAssert("text box contains '727'", () => chatOverlayTextBox.Text == "727"); + + AddStep("select PM channel", () => clickDrawable(getChannelListItem(testPMChannel))); + AddAssert("text box contains 'hello'", () => chatOverlayTextBox.Text == "hello"); + AddStep("click close button", () => + { + ChannelListItemCloseButton closeButton = getChannelListItem(testPMChannel).ChildrenOfType().Single(); + clickDrawable(closeButton); + }); + + AddAssert("listing is visible", () => listingIsVisible); + AddAssert("text box contains 'channel 2'", () => chatOverlayTextBox.Text == "number 2"); + AddUntilStep("only channel 2 visible", () => + { + IEnumerable listingItems = chatOverlay.ChildrenOfType() + .Where(item => item.IsPresent); + return listingItems.Count() == 1 && listingItems.Single().Channel == testChannel2; + }); + } + private void joinTestChannel(int i) { AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i])); diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index ebd5e12acb..d7f79d3e30 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -50,6 +50,8 @@ namespace osu.Game.Tests.Visual.Online private ChannelManager channelManager; private TestStandAloneChatDisplay chatDisplay; + private TestStandAloneChatDisplay chatWithTextBox; + private TestStandAloneChatDisplay chatWithTextBox2; private int messageIdSequence; private Channel testChannel; @@ -78,7 +80,7 @@ namespace osu.Game.Tests.Visual.Online private void reinitialiseDrawableDisplay() { - Children = new[] + Children = new Drawable[] { chatDisplay = new TestStandAloneChatDisplay { @@ -88,13 +90,28 @@ namespace osu.Game.Tests.Visual.Online Size = new Vector2(400, 80), Channel = { Value = testChannel }, }, - new TestStandAloneChatDisplay(true) + new FillFlowContainer { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, Margin = new MarginPadding(20), - Size = new Vector2(400, 150), - Channel = { Value = testChannel }, + Children = new[] + { + chatWithTextBox = new TestStandAloneChatDisplay(true) + { + Margin = new MarginPadding(20), + Size = new Vector2(400, 150), + Channel = { Value = testChannel }, + }, + chatWithTextBox2 = new TestStandAloneChatDisplay(true) + { + Margin = new MarginPadding(20), + Size = new Vector2(400, 150), + Channel = { Value = testChannel }, + }, + } } }; } @@ -351,6 +368,13 @@ namespace osu.Game.Tests.Visual.Online checkScrolledToBottom(); } + [Test] + public void TestTextBoxSync() + { + AddStep("type 'hello' to text box 1", () => chatWithTextBox.ChildrenOfType().Single().Text = "hello"); + AddAssert("text box 2 contains 'hello'", () => chatWithTextBox2.ChildrenOfType().Single().Text == "hello"); + } + private void fillChat(int count = 10) { AddStep("fill chat", () => From c326745f96747453453f49f5d3e51e494f4682fd Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 28 Dec 2022 15:12:57 -0800 Subject: [PATCH 104/138] Save / sync chat text box messages per channel --- osu.Game/Online/Chat/Channel.cs | 5 +++++ osu.Game/Online/Chat/StandAloneChatDisplay.cs | 8 ++++++++ osu.Game/Overlays/Chat/ChatTextBar.cs | 11 ++++++++--- osu.Game/Overlays/Chat/ChatTextBox.cs | 1 - 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 24b384b1d4..761e8aba8d 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -98,6 +98,11 @@ namespace osu.Game.Online.Chat /// public Bindable HighlightedMessage = new Bindable(); + /// + /// The current text box message while in this . + /// + public Bindable TextBoxMessage = new Bindable(string.Empty); + [JsonConstructor] public Channel() { diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 7fd6f99102..43da9a1d43 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -87,6 +87,14 @@ namespace osu.Game.Online.Chat channelManager ??= manager; } + protected override void LoadComplete() + { + base.LoadComplete(); + + if (channelManager != null) + TextBox?.Current.BindTo(channelManager.CurrentChannel.Value.TextBoxMessage); + } + protected virtual StandAloneDrawableChannel CreateDrawableChannel(Channel channel) => new StandAloneDrawableChannel(channel); diff --git a/osu.Game/Overlays/Chat/ChatTextBar.cs b/osu.Game/Overlays/Chat/ChatTextBar.cs index bcf5c1a409..682c96a695 100644 --- a/osu.Game/Overlays/Chat/ChatTextBar.cs +++ b/osu.Game/Overlays/Chat/ChatTextBar.cs @@ -128,9 +128,8 @@ namespace osu.Game.Overlays.Chat chattingTextContainer.FadeTo(showSearch ? 0 : 1); searchIconContainer.FadeTo(showSearch ? 1 : 0); - // Clear search terms if any exist when switching back to chat mode - if (!showSearch) - OnSearchTermsChanged?.Invoke(string.Empty); + if (showSearch) + OnSearchTermsChanged?.Invoke(chatTextBox.Current.Value); }, true); currentChannel.BindValueChanged(change => @@ -151,6 +150,12 @@ namespace osu.Game.Overlays.Chat chattingText.Text = ChatStrings.TalkingIn(newChannel.Name); break; } + + if (change.OldValue != null) + chatTextBox.Current.UnbindFrom(change.OldValue.TextBoxMessage); + + if (newChannel != null) + chatTextBox.Current.BindTo(newChannel.TextBoxMessage); }, true); } diff --git a/osu.Game/Overlays/Chat/ChatTextBox.cs b/osu.Game/Overlays/Chat/ChatTextBox.cs index 780c85a9c1..7cd005698e 100644 --- a/osu.Game/Overlays/Chat/ChatTextBox.cs +++ b/osu.Game/Overlays/Chat/ChatTextBox.cs @@ -24,7 +24,6 @@ namespace osu.Game.Overlays.Chat bool showSearch = change.NewValue; PlaceholderText = showSearch ? HomeStrings.SearchPlaceholder : ChatStrings.InputPlaceholder; - Text = string.Empty; }, true); } From 70dbb8edac4a2f7ed7513ade3e186cddfb95adfd Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 29 Dec 2022 01:37:37 -0800 Subject: [PATCH 105/138] Fix stand alone chat display textbox not binding to local channel --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 43da9a1d43..0a5434822b 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -87,14 +87,6 @@ namespace osu.Game.Online.Chat channelManager ??= manager; } - protected override void LoadComplete() - { - base.LoadComplete(); - - if (channelManager != null) - TextBox?.Current.BindTo(channelManager.CurrentChannel.Value.TextBoxMessage); - } - protected virtual StandAloneDrawableChannel CreateDrawableChannel(Channel channel) => new StandAloneDrawableChannel(channel); @@ -119,8 +111,13 @@ namespace osu.Game.Online.Chat { drawableChannel?.Expire(); + if (e.OldValue != null) + TextBox?.Current.UnbindFrom(e.OldValue.TextBoxMessage); + if (e.NewValue == null) return; + TextBox?.Current.BindTo(e.NewValue.TextBoxMessage); + drawableChannel = CreateDrawableChannel(e.NewValue); drawableChannel.CreateChatLineAction = CreateMessage; drawableChannel.Padding = new MarginPadding { Bottom = postingTextBox ? text_box_height : 0 }; From cacc23204d2a94d79e947a495444d95506ce27f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Dec 2022 13:16:13 +0100 Subject: [PATCH 106/138] Add failing test coverage --- osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index 0bc42b06dd..acb68fc9cd 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -201,6 +201,22 @@ namespace osu.Game.Tests.Visual.Menus AddAssert("volume not changed", () => Audio.Volume.Value == 0.5); } + [Test] + public void TestRulesetSelectorOverflow() + { + AddStep("set toolbar width", () => + { + toolbar.RelativeSizeAxes = Axes.None; + toolbar.Width = 0; + }); + AddStep("move mouse over news toggle button", () => + { + var button = toolbar.ChildrenOfType().Single(); + InputManager.MoveMouseTo(button); + }); + AddAssert("no ruleset toggle buttons hovered", () => !toolbar.ChildrenOfType().Any(button => button.IsHovered)); + } + public partial class TestToolbar : Toolbar { public new Bindable OverlayActivationMode => base.OverlayActivationMode as Bindable; From c5f7da9a4ecb6f9d1810653d09adacbae0d877a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Dec 2022 13:18:59 +0100 Subject: [PATCH 107/138] Fix hover propagating through toolbar buttons Closes #21920. Weirdly enough this was semeingly fixed once before in ancient times in 3891f467a34562d2d921fe996654802d0b29ab0b, but then unfixed again in 566e09083f06b87591c7745d82adf0d831a6066f. The second change is no longer needed since the toolbar became opaque in #9447. --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index ea5fc5bb38..4193e52584 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -179,7 +179,7 @@ namespace osu.Game.Overlays.Toolbar HoverBackground.FadeIn(200); tooltipContainer.FadeIn(100); - return base.OnHover(e); + return true; } protected override void OnHoverLost(HoverLostEvent e) From 0fcf10e10a0bdfd121c47b81649769a3e70a92f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Dec 2022 22:35:13 +0800 Subject: [PATCH 108/138] Also support hard links on macOS --- osu.Game/IO/HardLinkHelper.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index ebf2d2bd71..619bfdad6e 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -14,8 +14,8 @@ namespace osu.Game.IO { public static bool CheckAvailability(string testDestinationPath, string testSourcePath) { - // TODO: Add macOS support for hardlinks. - if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows && RuntimeInfo.OS != RuntimeInfo.Platform.Linux) + // For simplicity, only support desktop operating systems for now. + if (!RuntimeInfo.IsDesktop) return false; const string test_filename = "_hard_link_test"; @@ -59,7 +59,7 @@ namespace osu.Game.IO /// using platform-specific native methods. /// /// - /// Hard links are only available on Windows and Linux. + /// Hard links are only available on desktop platforms. /// /// Whether the hard link was successfully created. public static bool TryCreateHardLink(string destinationPath, string sourcePath) @@ -70,6 +70,7 @@ namespace osu.Game.IO return CreateHardLink(destinationPath, sourcePath, IntPtr.Zero); case RuntimeInfo.Platform.Linux: + case RuntimeInfo.Platform.macOS: return link(sourcePath, destinationPath) == 0; default: @@ -95,6 +96,7 @@ namespace osu.Game.IO break; case RuntimeInfo.Platform.Linux: + case RuntimeInfo.Platform.macOS: if (stat(filePath, out var statbuf) == 0) result = (int)statbuf.st_nlink; From ccf713c885c1dd77155a22d95db00158f45ae6d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Dec 2022 23:18:12 +0800 Subject: [PATCH 109/138] Fix incorrect hard link validity check in stable import screen --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 2e3ab823f7..0a2274575f 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.AddLink("Learn more about how \"hard links\" work", LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links"); } - else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows && RuntimeInfo.OS != RuntimeInfo.Platform.Linux) + else if (!RuntimeInfo.IsDesktop) copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; else { From f7febdac5e20ba71eb1dee4c64dd1916a558135a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Dec 2022 22:48:33 +0100 Subject: [PATCH 110/138] Add failing assertion --- osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index acb68fc9cd..aef6f9ade0 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -207,7 +207,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("set toolbar width", () => { toolbar.RelativeSizeAxes = Axes.None; - toolbar.Width = 0; + toolbar.Width = 400; }); AddStep("move mouse over news toggle button", () => { @@ -215,6 +215,7 @@ namespace osu.Game.Tests.Visual.Menus InputManager.MoveMouseTo(button); }); AddAssert("no ruleset toggle buttons hovered", () => !toolbar.ChildrenOfType().Any(button => button.IsHovered)); + AddUntilStep("toolbar gradient visible", () => toolbar.ChildrenOfType().Single().Children.All(d => d.Alpha > 0)); } public partial class TestToolbar : Toolbar From bf975eb48a89707d69b62ff1e2bebd434464ad2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Dec 2022 23:02:45 +0100 Subject: [PATCH 111/138] Fix toolbar gradient not showing when mouse is hovered over buttons --- osu.Game/Overlays/Toolbar/Toolbar.cs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index ac0f822f68..a559363bbf 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -13,6 +13,7 @@ using osuTK; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets; using osu.Framework.Input.Bindings; @@ -196,6 +197,9 @@ namespace osu.Game.Overlays.Toolbar public partial class ToolbarBackground : Container { + private InputManager inputManager; + private Bindable showGradient { get; } = new BindableBool(); + private readonly Box gradientBackground; public ToolbarBackground() @@ -220,15 +224,26 @@ namespace osu.Game.Overlays.Toolbar }; } - protected override bool OnHover(HoverEvent e) + protected override void LoadComplete() { - gradientBackground.FadeIn(transition_time, Easing.OutQuint); - return true; + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + showGradient.BindValueChanged(_ => updateState(), true); } - protected override void OnHoverLost(HoverLostEvent e) + protected override void Update() { - gradientBackground.FadeOut(transition_time, Easing.OutQuint); + base.Update(); + showGradient.Value = Contains(inputManager.CurrentState.Mouse.Position); + } + + private void updateState() + { + if (showGradient.Value) + gradientBackground.FadeIn(transition_time, Easing.OutQuint); + else + gradientBackground.FadeOut(transition_time, Easing.OutQuint); } } From f625c5d7446144455f2366a252392467de2a1038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Dec 2022 23:34:33 +0100 Subject: [PATCH 112/138] Fix gradient showing when toggling toolbar with mouse above window --- osu.Game/Overlays/Toolbar/Toolbar.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index a559363bbf..dbad866f74 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -235,7 +235,11 @@ namespace osu.Game.Overlays.Toolbar protected override void Update() { base.Update(); - showGradient.Value = Contains(inputManager.CurrentState.Mouse.Position); + + var currentMousePosition = inputManager.CurrentState.Mouse.Position; + // ensure that the gradient is not shown if the mouse is above the window. + // this is relevant when the background is moving due to the toolbar being toggled. + showGradient.Value = currentMousePosition.Y >= 0 && Contains(inputManager.CurrentState.Mouse.Position); } private void updateState() From 0d70f2c0fd3d070b49c6ce4225a88bed63932392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 09:07:48 +0100 Subject: [PATCH 113/138] Use alternative workaround --- osu.Game/Overlays/Toolbar/Toolbar.cs | 54 +++++++++++++++++++--------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index dbad866f74..f21ef0ee98 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -13,7 +13,6 @@ using osuTK; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets; using osu.Framework.Input.Bindings; @@ -66,9 +65,12 @@ namespace osu.Game.Overlays.Toolbar [BackgroundDependencyLoader(true)] private void load(OsuGame osuGame) { + ToolbarBackground background; + HoverInterceptor interceptor; + Children = new Drawable[] { - new ToolbarBackground(), + background = new ToolbarBackground(), new GridContainer { RelativeSizeAxes = Axes.Both, @@ -181,9 +183,15 @@ namespace osu.Game.Overlays.Toolbar }, }, } + }, + interceptor = new HoverInterceptor + { + RelativeSizeAxes = Axes.Both } }; + ((IBindable)background.ShowGradient).BindTo(interceptor.ReceivedHover); + if (osuGame != null) OverlayActivationMode.BindTo(osuGame.OverlayActivationMode); } @@ -197,8 +205,7 @@ namespace osu.Game.Overlays.Toolbar public partial class ToolbarBackground : Container { - private InputManager inputManager; - private Bindable showGradient { get; } = new BindableBool(); + public Bindable ShowGradient { get; } = new BindableBool(); private readonly Box gradientBackground; @@ -228,29 +235,42 @@ namespace osu.Game.Overlays.Toolbar { base.LoadComplete(); - inputManager = GetContainingInputManager(); - showGradient.BindValueChanged(_ => updateState(), true); - } - - protected override void Update() - { - base.Update(); - - var currentMousePosition = inputManager.CurrentState.Mouse.Position; - // ensure that the gradient is not shown if the mouse is above the window. - // this is relevant when the background is moving due to the toolbar being toggled. - showGradient.Value = currentMousePosition.Y >= 0 && Contains(inputManager.CurrentState.Mouse.Position); + ShowGradient.BindValueChanged(_ => updateState(), true); } private void updateState() { - if (showGradient.Value) + if (ShowGradient.Value) gradientBackground.FadeIn(transition_time, Easing.OutQuint); else gradientBackground.FadeOut(transition_time, Easing.OutQuint); } } + /// + /// Whenever the mouse cursor is within the bounds of the toolbar, we want the background gradient to show, for toolbar button descriptions to be legible. + /// Unfortunately we also need to ensure that the toolbar buttons handle hover, to prevent the possibility of multiple descriptions being shown + /// due to hover events passing through multiple buttons. + /// This drawable is a workaround, that when placed front-most in the toolbar, allows to see whether hover events have been propagated through it without handling them. + /// + private partial class HoverInterceptor : Drawable + { + public IBindable ReceivedHover => receivedHover; + private readonly Bindable receivedHover = new BindableBool(); + + protected override bool OnHover(HoverEvent e) + { + receivedHover.Value = true; + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + receivedHover.Value = false; + base.OnHoverLost(e); + } + } + protected override void UpdateState(ValueChangedEvent state) { bool blockShow = hiddenByUser || OverlayActivationMode.Value == OverlayActivation.Disabled; From 3c32a50c125bf4e9b28b26298277bb80cc38cb0f Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 30 Dec 2022 21:19:46 +0900 Subject: [PATCH 114/138] add new accuracy counter display --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 14 ++++++++ .../Play/HUD/GameplayAccuracyCounter.cs | 32 ++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 4228840461..cec2b7e6b9 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -39,6 +39,16 @@ namespace osu.Game.Rulesets.Scoring /// public readonly BindableDouble Accuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; + /// + /// The accuracy which increase from 0%. + /// + public readonly BindableDouble IncreaseAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; + + /// + /// The accuracy which Decrease from 100%. + /// + public readonly BindableDouble DecreaseAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; + /// /// The current combo. /// @@ -264,6 +274,10 @@ namespace osu.Game.Rulesets.Scoring private void updateScore() { Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; + IncreaseAccuracy.Value = maximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / maximumScoringValues.BaseScore : 0; + DecreaseAccuracy.Value = maximumScoringValues.BaseScore > 0 + ? (double)(maximumScoringValues.BaseScore - (currentMaximumScoringValues.BaseScore - currentScoringValues.BaseScore)) / maximumScoringValues.BaseScore + : 1; TotalScore.Value = computeScore(Mode.Value, currentScoringValues, maximumScoringValues); } diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index 1933193515..f8848f57b9 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -4,6 +4,8 @@ #nullable disable using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Scoring; @@ -11,10 +13,38 @@ namespace osu.Game.Screens.Play.HUD { public abstract partial class GameplayAccuracyCounter : PercentageCounter { + [SettingSource("Accuracy Display Mode")] + public Bindable AccType { get; } = new Bindable(); + [BackgroundDependencyLoader] private void load(ScoreProcessor scoreProcessor) { - Current.BindTo(scoreProcessor.Accuracy); + AccType.BindValueChanged(mod => + { + Current.UnbindBindings(); + + switch (mod.NewValue) + { + case AccuracyType.Current: + Current.BindTo(scoreProcessor.Accuracy); + break; + + case AccuracyType.Increase: + Current.BindTo(scoreProcessor.IncreaseAccuracy); + break; + + case AccuracyType.Decrease: + Current.BindTo(scoreProcessor.DecreaseAccuracy); + break; + } + }, true); + } + + public enum AccuracyType + { + Current, + Increase, + Decrease } } } From 784fe7ecf2278bf112f4a101d37a290ad028b67f Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 30 Dec 2022 23:06:10 +0900 Subject: [PATCH 115/138] rename `AccType` to `AccuracyDisplay` --- osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index f8848f57b9..a862e5e230 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -14,12 +14,12 @@ namespace osu.Game.Screens.Play.HUD public abstract partial class GameplayAccuracyCounter : PercentageCounter { [SettingSource("Accuracy Display Mode")] - public Bindable AccType { get; } = new Bindable(); + public Bindable AccuracyDisplay { get; } = new Bindable(); [BackgroundDependencyLoader] private void load(ScoreProcessor scoreProcessor) { - AccType.BindValueChanged(mod => + AccuracyDisplay.BindValueChanged(mod => { Current.UnbindBindings(); From 8beb168be9775b93947af36ab8d2ca54cf22a536 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 30 Dec 2022 23:24:20 +0900 Subject: [PATCH 116/138] remove nullable disabled --- osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index a862e5e230..f7916d833a 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Configuration; From d60349c7c606ba8a6b27a9b38d3448b609b89c0b Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 30 Dec 2022 23:24:41 +0900 Subject: [PATCH 117/138] add description --- osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index f7916d833a..ba96260f51 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -1,6 +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.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Configuration; @@ -11,7 +12,7 @@ namespace osu.Game.Screens.Play.HUD { public abstract partial class GameplayAccuracyCounter : PercentageCounter { - [SettingSource("Accuracy Display Mode")] + [SettingSource("Accuracy Display Mode", "Which Accuracy will display")] public Bindable AccuracyDisplay { get; } = new Bindable(); [BackgroundDependencyLoader] @@ -23,7 +24,7 @@ namespace osu.Game.Screens.Play.HUD switch (mod.NewValue) { - case AccuracyType.Current: + case AccuracyType.Rolling: Current.BindTo(scoreProcessor.Accuracy); break; @@ -40,8 +41,13 @@ namespace osu.Game.Screens.Play.HUD public enum AccuracyType { - Current, + [Description("Rolling")] + Rolling, + + [Description("Best achievable")] Increase, + + [Description("Worst achievable")] Decrease } } From 6d42cc5a360c4907357802e1720c2b563c0ac3d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 20:30:58 +0100 Subject: [PATCH 118/138] Naming pass --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 +++--- .../Play/HUD/GameplayAccuracyCounter.cs | 28 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index cec2b7e6b9..c379691c52 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -42,12 +42,12 @@ namespace osu.Game.Rulesets.Scoring /// /// The accuracy which increase from 0%. /// - public readonly BindableDouble IncreaseAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; + public readonly BindableDouble MinimumAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; /// /// The accuracy which Decrease from 100%. /// - public readonly BindableDouble DecreaseAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; + public readonly BindableDouble MaximumAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; /// /// The current combo. @@ -274,8 +274,8 @@ namespace osu.Game.Rulesets.Scoring private void updateScore() { Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; - IncreaseAccuracy.Value = maximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / maximumScoringValues.BaseScore : 0; - DecreaseAccuracy.Value = maximumScoringValues.BaseScore > 0 + MinimumAccuracy.Value = maximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / maximumScoringValues.BaseScore : 0; + MaximumAccuracy.Value = maximumScoringValues.BaseScore > 0 ? (double)(maximumScoringValues.BaseScore - (currentMaximumScoringValues.BaseScore - currentScoringValues.BaseScore)) / maximumScoringValues.BaseScore : 1; TotalScore.Value = computeScore(Mode.Value, currentScoringValues, maximumScoringValues); diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index ba96260f51..32e7827be8 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -12,8 +12,8 @@ namespace osu.Game.Screens.Play.HUD { public abstract partial class GameplayAccuracyCounter : PercentageCounter { - [SettingSource("Accuracy Display Mode", "Which Accuracy will display")] - public Bindable AccuracyDisplay { get; } = new Bindable(); + [SettingSource("Accuracy display mode", "Which accuracy mode should be displayed.")] + public Bindable AccuracyDisplay { get; } = new Bindable(); [BackgroundDependencyLoader] private void load(ScoreProcessor scoreProcessor) @@ -24,31 +24,31 @@ namespace osu.Game.Screens.Play.HUD switch (mod.NewValue) { - case AccuracyType.Rolling: + case AccuracyDisplayMode.Standard: Current.BindTo(scoreProcessor.Accuracy); break; - case AccuracyType.Increase: - Current.BindTo(scoreProcessor.IncreaseAccuracy); + case AccuracyDisplayMode.MinimumAchievable: + Current.BindTo(scoreProcessor.MinimumAccuracy); break; - case AccuracyType.Decrease: - Current.BindTo(scoreProcessor.DecreaseAccuracy); + case AccuracyDisplayMode.MaximumAchievable: + Current.BindTo(scoreProcessor.MaximumAccuracy); break; } }, true); } - public enum AccuracyType + public enum AccuracyDisplayMode { - [Description("Rolling")] - Rolling, + [Description("Standard")] + Standard, - [Description("Best achievable")] - Increase, + [Description("Maximum achievable")] + MaximumAchievable, - [Description("Worst achievable")] - Decrease + [Description("Minimum achievable")] + MinimumAchievable } } } From bb2822a175a44604a4bd2b08b04ff63c30bad788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 20:43:23 +0100 Subject: [PATCH 119/138] xmldoc pass --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index c379691c52..ab4422b2ef 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -40,12 +40,14 @@ namespace osu.Game.Rulesets.Scoring public readonly BindableDouble Accuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; /// - /// The accuracy which increase from 0%. + /// The minimum achievable accuracy for the whole beatmap at this stage of gameplay. + /// Assumes that all objects that have not been judged yet will receive the minimum hit result. /// public readonly BindableDouble MinimumAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; /// - /// The accuracy which Decrease from 100%. + /// The maximum achievable accuracy for the whole beatmap at this stage of gameplay. + /// Assumes that all objects that have not been judged yet will receive the maximum hit result. /// public readonly BindableDouble MaximumAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; From 8ace635249edccfc3eae7ac2f1519a821d518674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 20:43:47 +0100 Subject: [PATCH 120/138] Adjust minimum values --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index ab4422b2ef..2fec68f0b0 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -43,13 +43,13 @@ namespace osu.Game.Rulesets.Scoring /// The minimum achievable accuracy for the whole beatmap at this stage of gameplay. /// Assumes that all objects that have not been judged yet will receive the minimum hit result. /// - public readonly BindableDouble MinimumAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; + public readonly BindableDouble MinimumAccuracy = new BindableDouble { MinValue = 0, MaxValue = 1 }; /// /// The maximum achievable accuracy for the whole beatmap at this stage of gameplay. /// Assumes that all objects that have not been judged yet will receive the maximum hit result. /// - public readonly BindableDouble MaximumAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; + public readonly BindableDouble MaximumAccuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; /// /// The current combo. From ae026e2d2db216ee35aeeb776f677af84fad512e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 20:52:05 +0100 Subject: [PATCH 121/138] Add test coverage --- .../Gameplay/TestSceneScoreProcessor.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 3ce7aa72d9..90c7688443 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Judgements; @@ -139,6 +140,29 @@ namespace osu.Game.Tests.Gameplay Assert.That(score.MaximumStatistics[HitResult.LargeBonus], Is.EqualTo(1)); } + [Test] + public void TestAccuracyModes() + { + var beatmap = new Beatmap + { + HitObjects = Enumerable.Range(0, 4).Select(_ => new TestHitObject(HitResult.Great)).ToList() + }; + + var scoreProcessor = new ScoreProcessor(new OsuRuleset()); + scoreProcessor.ApplyBeatmap(beatmap); + + Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(1)); + Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo(0)); + Assert.That(scoreProcessor.MaximumAccuracy.Value, Is.EqualTo(1)); + + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.Great }); + + Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo((double)(100 + 300) / (2 * 300)).Within(Precision.DOUBLE_EPSILON)); + Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo((double)(100 + 300) / (4 * 300)).Within(Precision.DOUBLE_EPSILON)); + Assert.That(scoreProcessor.MaximumAccuracy.Value, Is.EqualTo((double)(100 + 3 * 300) / (4 * 300)).Within(Precision.DOUBLE_EPSILON)); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } From 6ed474d4fbb1be6523bbf809273dee21748bf926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 20:56:54 +0100 Subject: [PATCH 122/138] Rearrange formula for maximum accuracy Feels like it's easier to understand this way. The difference of the maximum scoring values for the entire beatmap and the max values for the part of the beatmap that has already been played represents the act of filling the rest of the unjudged objects with maximum results. --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 2fec68f0b0..13276a8748 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -278,7 +278,7 @@ namespace osu.Game.Rulesets.Scoring Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; MinimumAccuracy.Value = maximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / maximumScoringValues.BaseScore : 0; MaximumAccuracy.Value = maximumScoringValues.BaseScore > 0 - ? (double)(maximumScoringValues.BaseScore - (currentMaximumScoringValues.BaseScore - currentScoringValues.BaseScore)) / maximumScoringValues.BaseScore + ? (double)(currentScoringValues.BaseScore + (maximumScoringValues.BaseScore - currentMaximumScoringValues.BaseScore)) / maximumScoringValues.BaseScore : 1; TotalScore.Value = computeScore(Mode.Value, currentScoringValues, maximumScoringValues); } From 7580ab78be796e9bf80b2b8dd8a3a96dbca2a904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 21:08:48 +0100 Subject: [PATCH 123/138] Move binding to `LoadComplete()` --- osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index 32e7827be8..210f96ce46 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -15,9 +15,13 @@ namespace osu.Game.Screens.Play.HUD [SettingSource("Accuracy display mode", "Which accuracy mode should be displayed.")] public Bindable AccuracyDisplay { get; } = new Bindable(); - [BackgroundDependencyLoader] - private void load(ScoreProcessor scoreProcessor) + [Resolved] + private ScoreProcessor scoreProcessor { get; set; } = null!; + + protected override void LoadComplete() { + base.LoadComplete(); + AccuracyDisplay.BindValueChanged(mod => { Current.UnbindBindings(); From 6509d3538c203f41049218899b1dd0471dcfa385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 21:12:29 +0100 Subject: [PATCH 124/138] Fix counter initially rolling down from 100% to 0% in minimum achievable mode --- osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index 210f96ce46..ca7fef8f73 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -41,6 +41,12 @@ namespace osu.Game.Screens.Play.HUD break; } }, true); + + // if the accuracy counter is using the "minimum achievable" mode, + // then its initial value is 0%, rather than the 100% that the base PercentageCounter assumes. + // to counteract this, manually finish transforms on DisplayedCount once after the initial callback above + // to stop it from rolling down from 100% to 0%. + FinishTransforms(targetMember: nameof(DisplayedCount)); } public enum AccuracyDisplayMode From 87250ad84707e5b7b1a5041af9fc102b9da81f43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Jan 2023 14:32:28 +0800 Subject: [PATCH 125/138] Add search keywords for beatmap colours / hitsound overrides --- osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs index e8db5fe58a..da5fc519e6 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs @@ -32,11 +32,13 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay }, new SettingsCheckbox { + Keywords = new[] { "combo", "override" }, LabelText = SkinSettingsStrings.BeatmapColours, Current = config.GetBindable(OsuSetting.BeatmapColours) }, new SettingsCheckbox { + Keywords = new[] { "samples", "override" }, LabelText = SkinSettingsStrings.BeatmapHitsounds, Current = config.GetBindable(OsuSetting.BeatmapHitsounds) }, From 9a4f0cad2c5306b6c3076b7ab5262002386f9992 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Jan 2023 17:48:04 +0800 Subject: [PATCH 126/138] Fix incorrect domain root being used for recent activity entries on profile overlay Closes https://github.com/ppy/osu/issues/21980. --- .../Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index 4a8b020e33..c3fa467e5f 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -224,7 +224,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent private void addBeatmapsetLink() => content.AddLink(activity.Beatmapset?.Title, LinkAction.OpenBeatmapSet, getLinkArgument(activity.Beatmapset?.Url), creationParameters: t => t.Font = getLinkFont()); - private string getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.APIEndpointUrl}{url}").Argument.ToString(); + private string getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.WebsiteRootUrl}{url}").Argument.ToString(); private FontUsage getLinkFont(FontWeight fontWeight = FontWeight.Regular) => OsuFont.GetFont(size: font_size, weight: fontWeight, italics: true); From 88e90d5fa098f7f19fa2986d44aa9a02336ab0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 13:17:59 +0100 Subject: [PATCH 127/138] Enable NRT in user profile overlay --- .../Profile/Header/BottomHeaderContainer.cs | 14 ++++---- .../Profile/Header/CentreHeaderContainer.cs | 10 +++--- .../Header/Components/ExpandDetailsButton.cs | 8 ++--- .../Header/Components/FollowersButton.cs | 4 +-- .../Profile/Header/Components/LevelBadge.cs | 13 ++++--- .../Header/Components/LevelProgressBar.cs | 10 +++--- .../Components/MappingSubscribersButton.cs | 4 +-- .../Header/Components/MessageUserButton.cs | 24 ++++++------- .../Components/OverlinedInfoContainer.cs | 2 -- .../Components/OverlinedTotalPlayTime.cs | 8 ++--- .../Header/Components/PreviousUsernames.cs | 10 +++--- .../Header/Components/ProfileHeaderButton.cs | 2 -- .../ProfileHeaderStatisticsButton.cs | 2 -- .../Components/ProfileRulesetSelector.cs | 7 ++-- .../Components/ProfileRulesetTabItem.cs | 2 -- .../Profile/Header/Components/RankGraph.cs | 8 ++--- .../Header/Components/SupporterIcon.cs | 8 ++--- .../Profile/Header/DetailHeaderContainer.cs | 18 +++++----- .../Profile/Header/MedalHeaderContainer.cs | 12 +++---- .../Profile/Header/TopHeaderContainer.cs | 24 ++++++------- osu.Game/Overlays/Profile/ProfileHeader.cs | 8 ++--- osu.Game/Overlays/Profile/ProfileSection.cs | 4 +-- .../Overlays/Profile/Sections/AboutSection.cs | 2 -- .../Sections/BeatmapMetadataContainer.cs | 6 ++-- .../Beatmaps/PaginatedBeatmapContainer.cs | 10 +++--- .../Profile/Sections/BeatmapsSection.cs | 2 -- .../Overlays/Profile/Sections/CounterPill.cs | 4 +-- .../Historical/ChartProfileSubsection.cs | 10 +++--- .../Historical/DrawableMostPlayedBeatmap.cs | 2 -- .../PaginatedMostPlayedBeatmapContainer.cs | 8 ++--- .../Historical/PlayHistorySubsection.cs | 6 ++-- .../Sections/Historical/ProfileLineChart.cs | 6 +--- .../Sections/Historical/ReplaysSubsection.cs | 6 ++-- .../Sections/Historical/UserHistoryGraph.cs | 6 +--- .../Profile/Sections/HistoricalSection.cs | 2 -- .../Kudosu/DrawableKudosuHistoryItem.cs | 4 +-- .../Profile/Sections/Kudosu/KudosuInfo.cs | 6 ++-- .../Kudosu/PaginatedKudosuHistoryContainer.cs | 8 ++--- .../Profile/Sections/KudosuSection.cs | 2 -- .../Profile/Sections/MedalsSection.cs | 2 -- .../Sections/PaginatedProfileSubsection.cs | 35 ++++++++++--------- .../Profile/Sections/ProfileItemContainer.cs | 2 -- .../Profile/Sections/ProfileSubsection.cs | 10 ++---- .../Sections/ProfileSubsectionHeader.cs | 4 +-- .../Sections/Ranks/DrawableProfileScore.cs | 13 +++---- .../Ranks/DrawableProfileWeightedScore.cs | 2 -- .../Sections/Ranks/PaginatedScoreContainer.cs | 8 ++--- .../Overlays/Profile/Sections/RanksSection.cs | 2 -- .../Sections/Recent/DrawableRecentActivity.cs | 17 +++++---- .../Profile/Sections/Recent/MedalIcon.cs | 2 -- .../PaginatedRecentActivityContainer.cs | 8 ++--- .../Profile/Sections/RecentSection.cs | 2 -- osu.Game/Overlays/Profile/UserGraph.cs | 25 +++++++------ osu.Game/Overlays/UserProfileOverlay.cs | 15 ++++---- osu.Game/Users/UserCoverBackground.cs | 12 +++---- 55 files changed, 172 insertions(+), 279 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 5ba3963a45..f5c7a3f87a 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using Humanizer; using osu.Framework.Allocation; @@ -25,15 +23,15 @@ namespace osu.Game.Overlays.Profile.Header { public partial class BottomHeaderContainer : CompositeDrawable { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); - private LinkFlowContainer topLinkContainer; - private LinkFlowContainer bottomLinkContainer; + private LinkFlowContainer topLinkContainer = null!; + private LinkFlowContainer bottomLinkContainer = null!; private Color4 iconColour; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; public BottomHeaderContainer() { @@ -78,7 +76,7 @@ namespace osu.Game.Overlays.Profile.Header User.BindValueChanged(user => updateDisplay(user.NewValue)); } - private void updateDisplay(APIUser user) + private void updateDisplay(APIUser? user) { topLinkContainer.Clear(); bottomLinkContainer.Clear(); @@ -164,7 +162,7 @@ namespace osu.Game.Overlays.Profile.Header private void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); - private bool tryAddInfo(IconUsage icon, string content, string link = null) + private bool tryAddInfo(IconUsage icon, string content, string? link = null) { if (string.IsNullOrEmpty(content)) return false; diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 5f6af7a6e2..5e4e1184a4 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; @@ -20,10 +18,10 @@ namespace osu.Game.Overlays.Profile.Header public partial class CentreHeaderContainer : CompositeDrawable { public readonly BindableBool DetailsVisible = new BindableBool(true); - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); - private OverlinedInfoContainer hiddenDetailGlobal; - private OverlinedInfoContainer hiddenDetailCountry; + private OverlinedInfoContainer hiddenDetailGlobal = null!; + private OverlinedInfoContainer hiddenDetailCountry = null!; public CentreHeaderContainer() { @@ -146,7 +144,7 @@ namespace osu.Game.Overlays.Profile.Header User.BindValueChanged(user => updateDisplay(user.NewValue)); } - private void updateDisplay(APIUser user) + private void updateDisplay(APIUser? user) { hiddenDetailGlobal.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; hiddenDetailCountry.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs index a778ddd2b1..e7a83c95d8 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -23,9 +21,9 @@ namespace osu.Game.Overlays.Profile.Header.Components public override LocalisableString TooltipText => DetailsVisible.Value ? CommonStrings.ButtonsCollapse : CommonStrings.ButtonsExpand; - private SpriteIcon icon; - private Sample sampleOpen; - private Sample sampleClose; + private SpriteIcon icon = null!; + private Sample? sampleOpen; + private Sample? sampleClose; protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds(); diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index c278a6c48b..c0bcec622a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; @@ -14,7 +12,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class FollowersButton : ProfileHeaderStatisticsButton { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); public override LocalisableString TooltipText => FriendsStrings.ButtonsDisabled; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs index ef2f35e9a8..8f84e69bac 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -20,11 +18,11 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class LevelBadge : CompositeDrawable, IHasTooltip { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); public LocalisableString TooltipText { get; private set; } - private OsuSpriteText levelText; + private OsuSpriteText levelText = null!; public LevelBadge() { @@ -53,10 +51,11 @@ namespace osu.Game.Overlays.Profile.Header.Components User.BindValueChanged(user => updateLevel(user.NewValue)); } - private void updateLevel(APIUser user) + private void updateLevel(APIUser? user) { - levelText.Text = user?.Statistics?.Level.Current.ToString() ?? "0"; - TooltipText = UsersStrings.ShowStatsLevel(user?.Statistics?.Level.Current.ToString()); + string level = user?.Statistics?.Level.Current.ToString() ?? "0"; + levelText.Text = level; + TooltipText = UsersStrings.ShowStatsLevel(level); } } } diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs index 0351230fb3..ea5ca0c8bd 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; @@ -21,12 +19,12 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class LevelProgressBar : CompositeDrawable, IHasTooltip { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); public LocalisableString TooltipText { get; } - private Bar levelProgressBar; - private OsuSpriteText levelProgressText; + private Bar levelProgressBar = null!; + private OsuSpriteText levelProgressText = null!; public LevelProgressBar() { @@ -61,7 +59,7 @@ namespace osu.Game.Overlays.Profile.Header.Components User.BindValueChanged(user => updateProgress(user.NewValue)); } - private void updateProgress(APIUser user) + private void updateProgress(APIUser? user) { levelProgressBar.Length = user?.Statistics?.Level.Progress / 100f ?? 0; levelProgressText.Text = user?.Statistics?.Level.Progress.ToLocalisableString("0'%'") ?? default; diff --git a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs index 887cf10cce..c600f7622a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; @@ -14,7 +12,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class MappingSubscribersButton : ProfileHeaderStatisticsButton { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); public override LocalisableString TooltipText => FollowsStrings.MappingFollowers; diff --git a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs index 4886324a22..3266e3c308 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -18,21 +16,21 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class MessageUserButton : ProfileHeaderButton { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); public override LocalisableString TooltipText => UsersStrings.CardSendMessage; - [Resolved(CanBeNull = true)] - private ChannelManager channelManager { get; set; } - - [Resolved(CanBeNull = true)] - private UserProfileOverlay userOverlay { get; set; } - - [Resolved(CanBeNull = true)] - private ChatOverlay chatOverlay { get; set; } + [Resolved] + private ChannelManager? channelManager { get; set; } [Resolved] - private IAPIProvider apiProvider { get; set; } + private UserProfileOverlay? userOverlay { get; set; } + + [Resolved] + private ChatOverlay? chatOverlay { get; set; } + + [Resolved] + private IAPIProvider apiProvider { get; set; } = null!; public MessageUserButton() { @@ -56,7 +54,7 @@ namespace osu.Game.Overlays.Profile.Header.Components chatOverlay?.Show(); }; - User.ValueChanged += e => Content.Alpha = !e.NewValue.PMFriendsOnly && apiProvider.LocalUser.Value.Id != e.NewValue.Id ? 1 : 0; + User.ValueChanged += e => Content.Alpha = e.NewValue != null && !e.NewValue.PMFriendsOnly && apiProvider.LocalUser.Value.Id != e.NewValue.Id ? 1 : 0; } } } diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs index 244f185e28..42b67865a0 100644 --- a/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs index c040f5a787..e9e277acd3 100644 --- a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -16,11 +14,11 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class OverlinedTotalPlayTime : CompositeDrawable, IHasTooltip { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); public LocalisableString TooltipText { get; set; } - private OverlinedInfoContainer info; + private OverlinedInfoContainer info = null!; public OverlinedTotalPlayTime() { @@ -41,7 +39,7 @@ namespace osu.Game.Overlays.Profile.Header.Components User.BindValueChanged(updateTime, true); } - private void updateTime(ValueChangedEvent user) + private void updateTime(ValueChangedEvent user) { TooltipText = (user.NewValue?.Statistics?.PlayTime ?? 0) / 3600 + " hours"; info.Content = formatTime(user.NewValue?.Statistics?.PlayTime); diff --git a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs index 0abc4de5ef..b722fe92e0 100644 --- a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs +++ b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Allocation; @@ -27,7 +25,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private const int width = 310; private const int move_offset = 15; - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); private readonly TextFlowContainer text; private readonly Box background; @@ -109,11 +107,11 @@ namespace osu.Game.Overlays.Profile.Header.Components User.BindValueChanged(onUserChanged, true); } - private void onUserChanged(ValueChangedEvent user) + private void onUserChanged(ValueChangedEvent user) { text.Text = string.Empty; - string[] usernames = user.NewValue?.PreviousUsernames; + string[]? usernames = user.NewValue?.PreviousUsernames; if (usernames?.Any() ?? false) { @@ -149,7 +147,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private partial class HoverIconContainer : Container { - public Action ActivateHover; + public Action? ActivateHover; public HoverIconContainer() { diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs index c4a46440d2..414ca4d077 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs index 5ad726a3ea..32c5ebee2c 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs index 72446bde3a..684ce9088e 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; @@ -12,12 +11,12 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class ProfileRulesetSelector : OverlayRulesetSelector { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); protected override void LoadComplete() { base.LoadComplete(); - User.BindValueChanged(u => SetDefaultRuleset(Rulesets.GetRuleset(u.NewValue?.PlayMode ?? "osu")), true); + User.BindValueChanged(u => SetDefaultRuleset(Rulesets.GetRuleset(u.NewValue?.PlayMode ?? "osu").AsNonNull()), true); } public void SetDefaultRuleset(RulesetInfo ruleset) diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs index 72adc96f9a..9caa7dd1bc 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 51531977d2..fe1069eea1 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -21,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { private const int ranked_days = 88; - public readonly Bindable Statistics = new Bindable(); + public readonly Bindable Statistics = new Bindable(); private readonly OsuSpriteText placeholder; @@ -42,11 +40,11 @@ namespace osu.Game.Overlays.Profile.Header.Components Statistics.BindValueChanged(statistics => updateStatistics(statistics.NewValue), true); } - private void updateStatistics(UserStatistics statistics) + private void updateStatistics(UserStatistics? statistics) { // checking both IsRanked and RankHistory is required. // see https://github.com/ppy/osu-web/blob/154ceafba0f35a1dd935df53ec98ae2ea5615f9f/resources/assets/lib/profile-page/rank-chart.tsx#L46 - int[] userRanks = statistics?.IsRanked == true ? statistics.RankHistory?.Data : null; + int[]? userRanks = statistics?.IsRanked == true ? statistics.RankHistory?.Data : null; Data = userRanks?.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); } diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index 4028eb1389..92e2017659 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -82,10 +80,10 @@ namespace osu.Game.Overlays.Profile.Header.Components } [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; - [BackgroundDependencyLoader(true)] - private void load(OsuGame game) + [BackgroundDependencyLoader] + private void load(OsuGame? game) { background.Colour = colours.Pink; diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 44986dc178..95fead1246 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -25,14 +23,14 @@ namespace osu.Game.Overlays.Profile.Header public partial class DetailHeaderContainer : CompositeDrawable { private readonly Dictionary scoreRankInfos = new Dictionary(); - private OverlinedInfoContainer medalInfo; - private OverlinedInfoContainer ppInfo; - private OverlinedInfoContainer detailGlobalRank; - private OverlinedInfoContainer detailCountryRank; - private FillFlowContainer fillFlow; - private RankGraph rankGraph; + private OverlinedInfoContainer medalInfo = null!; + private OverlinedInfoContainer ppInfo = null!; + private OverlinedInfoContainer detailGlobalRank = null!; + private OverlinedInfoContainer detailCountryRank = null!; + private FillFlowContainer? fillFlow; + private RankGraph rankGraph = null!; - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); private bool expanded = true; @@ -173,7 +171,7 @@ namespace osu.Game.Overlays.Profile.Header }; } - private void updateDisplay(APIUser user) + private void updateDisplay(APIUser? user) { medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0"; ppInfo.Content = user?.Statistics?.PP?.ToLocalisableString("#,##0") ?? (LocalisableString)"0"; diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index ab800d006d..79f9eba57d 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -20,9 +18,9 @@ namespace osu.Game.Overlays.Profile.Header { public partial class MedalHeaderContainer : CompositeDrawable { - private FillFlowContainer badgeFlowContainer; + private FillFlowContainer badgeFlowContainer = null!; - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -66,16 +64,16 @@ namespace osu.Game.Overlays.Profile.Header }; } - private CancellationTokenSource cancellationTokenSource; + private CancellationTokenSource? cancellationTokenSource; - private void updateDisplay(APIUser user) + private void updateDisplay(APIUser? user) { cancellationTokenSource?.Cancel(); cancellationTokenSource = new CancellationTokenSource(); badgeFlowContainer.Clear(); - var badges = user.Badges; + var badges = user?.Badges; if (badges?.Length > 0) { diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 8826dd982d..a6501f567f 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -29,19 +27,19 @@ namespace osu.Game.Overlays.Profile.Header { private const float avatar_size = 110; - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - private SupporterIcon supporterTag; - private UpdateableAvatar avatar; - private OsuSpriteText usernameText; - private ExternalLinkButton openUserExternally; - private OsuSpriteText titleText; - private UpdateableFlag userFlag; - private OsuSpriteText userCountryText; - private FillFlowContainer userStats; + private SupporterIcon supporterTag = null!; + private UpdateableAvatar avatar = null!; + private OsuSpriteText usernameText = null!; + private ExternalLinkButton openUserExternally = null!; + private OsuSpriteText titleText = null!; + private UpdateableFlag userFlag = null!; + private OsuSpriteText userCountryText = null!; + private FillFlowContainer userStats = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -176,7 +174,7 @@ namespace osu.Game.Overlays.Profile.Header User.BindValueChanged(user => updateUser(user.NewValue)); } - private void updateUser(APIUser user) + private void updateUser(APIUser? user) { avatar.User = user; usernameText.Text = user?.Username ?? string.Empty; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 8443678989..2b8d2503e0 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -20,9 +18,9 @@ namespace osu.Game.Overlays.Profile { public partial class ProfileHeader : TabControlOverlayHeader { - private UserCoverBackground coverContainer; + private UserCoverBackground coverContainer = null!; - public Bindable User = new Bindable(); + public Bindable User = new Bindable(); private CentreHeaderContainer centreHeaderContainer; private DetailHeaderContainer detailHeaderContainer; @@ -102,7 +100,7 @@ namespace osu.Game.Overlays.Profile protected override OverlayTitle CreateTitle() => new ProfileHeaderTitle(); - private void updateDisplay(APIUser user) => coverContainer.User = user; + private void updateDisplay(APIUser? user) => coverContainer.User = user; private partial class ProfileHeaderTitle : OverlayTitle { diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index fc99050f73..62b40545df 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -31,7 +29,7 @@ namespace osu.Game.Overlays.Profile protected override Container Content => content; - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); protected ProfileSection() { diff --git a/osu.Game/Overlays/Profile/Sections/AboutSection.cs b/osu.Game/Overlays/Profile/Sections/AboutSection.cs index ac3dca5107..69a8b23412 100644 --- a/osu.Game/Overlays/Profile/Sections/AboutSection.cs +++ b/osu.Game/Overlays/Profile/Sections/AboutSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index 5a80a4d444..499c572eac 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -25,8 +23,8 @@ namespace osu.Game.Overlays.Profile.Sections AutoSizeAxes = Axes.Both; } - [BackgroundDependencyLoader(true)] - private void load(BeatmapSetOverlay beatmapSetOverlay) + [BackgroundDependencyLoader] + private void load(BeatmapSetOverlay? beatmapSetOverlay) { Action = () => { diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 56d9fb9ec6..e327d71d25 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -24,7 +22,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps protected override int InitialItemsCount => type == BeatmapSetType.Graveyard ? 2 : 6; - public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, LocalisableString headerText) + public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, LocalisableString headerText) : base(user, headerText) { this.type = type; @@ -66,10 +64,10 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps } } - protected override APIRequest> CreateRequest(PaginationParameters pagination) => - new GetUserBeatmapsRequest(User.Value.Id, type, pagination); + protected override APIRequest> CreateRequest(APIUser user, PaginationParameters pagination) => + new GetUserBeatmapsRequest(user.Id, type, pagination); - protected override Drawable CreateDrawableItem(APIBeatmapSet model) => model.OnlineID > 0 + protected override Drawable? CreateDrawableItem(APIBeatmapSet model) => model.OnlineID > 0 ? new BeatmapCardNormal(model) { Anchor = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs index cf80ebd66f..3b304a79ef 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile.Sections.Beatmaps; diff --git a/osu.Game/Overlays/Profile/Sections/CounterPill.cs b/osu.Game/Overlays/Profile/Sections/CounterPill.cs index c93cdb84f2..74cd4b218b 100644 --- a/osu.Game/Overlays/Profile/Sections/CounterPill.cs +++ b/osu.Game/Overlays/Profile/Sections/CounterPill.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; @@ -18,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections { public readonly BindableInt Current = new BindableInt(); - private OsuSpriteText counter; + private OsuSpriteText counter = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs index e0837320b2..c532c693ec 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; @@ -15,14 +13,14 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public abstract partial class ChartProfileSubsection : ProfileSubsection { - private ProfileLineChart chart; + private ProfileLineChart chart = null!; /// /// Text describing the value being plotted on the graph, which will be displayed as a prefix to the value in the history graph tooltip. /// protected abstract LocalisableString GraphCounterName { get; } - protected ChartProfileSubsection(Bindable user, LocalisableString headerText) + protected ChartProfileSubsection(Bindable user, LocalisableString headerText) : base(user, headerText) { } @@ -46,7 +44,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical User.BindValueChanged(onUserChanged, true); } - private void onUserChanged(ValueChangedEvent e) + private void onUserChanged(ValueChangedEvent e) { var values = GetValues(e.NewValue); @@ -86,6 +84,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical return filledHistoryEntries.ToArray(); } - protected abstract APIUserHistoryCount[] GetValues(APIUser user); + protected abstract APIUserHistoryCount[]? GetValues(APIUser? user); } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index a31ee88f3b..53447a971b 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 222969bdfd..515c1fd146 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -18,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public partial class PaginatedMostPlayedBeatmapContainer : PaginatedProfileSubsection { - public PaginatedMostPlayedBeatmapContainer(Bindable user) + public PaginatedMostPlayedBeatmapContainer(Bindable user) : base(user, UsersStrings.ShowExtraHistoricalMostPlayedTitle) { } @@ -31,8 +29,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical protected override int GetCount(APIUser user) => user.BeatmapPlayCountsCount; - protected override APIRequest> CreateRequest(PaginationParameters pagination) => - new GetUserMostPlayedBeatmapsRequest(User.Value.Id, pagination); + protected override APIRequest> CreateRequest(APIUser user, PaginationParameters pagination) => + new GetUserMostPlayedBeatmapsRequest(user.Id, pagination); protected override Drawable CreateDrawableItem(APIUserMostPlayedBeatmap mostPlayed) => new DrawableMostPlayedBeatmap(mostPlayed); diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs index 186bf73898..c8a6b1da31 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Game.Online.API.Requests.Responses; @@ -14,11 +12,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { protected override LocalisableString GraphCounterName => UsersStrings.ShowExtraHistoricalMonthlyPlaycountsCountLabel; - public PlayHistorySubsection(Bindable user) + public PlayHistorySubsection(Bindable user) : base(user, UsersStrings.ShowExtraHistoricalMonthlyPlaycountsTitle) { } - protected override APIUserHistoryCount[] GetValues(APIUser user) => user?.MonthlyPlayCounts; + protected override APIUserHistoryCount[]? GetValues(APIUser? user) => user?.MonthlyPlayCounts; } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index da86d870fc..5711bfc046 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -1,11 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; -using JetBrains.Annotations; using System; using System.Linq; using osu.Game.Graphics.Sprites; @@ -22,9 +19,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public partial class ProfileLineChart : CompositeDrawable { - private APIUserHistoryCount[] values; + private APIUserHistoryCount[] values = Array.Empty(); - [NotNull] public APIUserHistoryCount[] Values { get => values; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs index d1d1e76e14..ac7c616a64 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Game.Online.API.Requests.Responses; @@ -14,11 +12,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { protected override LocalisableString GraphCounterName => UsersStrings.ShowExtraHistoricalReplaysWatchedCountsCountLabel; - public ReplaysSubsection(Bindable user) + public ReplaysSubsection(Bindable user) : base(user, UsersStrings.ShowExtraHistoricalReplaysWatchedCountsTitle) { } - protected override APIUserHistoryCount[] GetValues(APIUser user) => user?.ReplaysWatchedCounts; + protected override APIUserHistoryCount[]? GetValues(APIUser? user) => user?.ReplaysWatchedCounts; } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index 3f68ffdd0e..310607e757 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Localisation; using osu.Game.Online.API.Requests.Responses; @@ -17,8 +14,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { private readonly LocalisableString tooltipCounterName; - [CanBeNull] - public APIUserHistoryCount[] Values + public APIUserHistoryCount[]? Values { set => Data = value?.Select(v => new KeyValuePair(v.Date, v.Count)).ToArray(); } diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index 13e0aefc65..19f7a32d4d 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Online.API.Requests; diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 122b20cbfc..e7991acb89 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,7 +18,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu private const int height = 25; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; private readonly APIKudosuHistory historyItem; private readonly LinkFlowContainer linkFlowContainer; diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index 2b4d58b845..acf4827dfd 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osuTK; using osu.Framework.Graphics; @@ -22,9 +20,9 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu { public partial class KudosuInfo : Container { - private readonly Bindable user = new Bindable(); + private readonly Bindable user = new Bindable(); - public KudosuInfo(Bindable user) + public KudosuInfo(Bindable user) { this.user.BindTo(user); CountSection total; diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs index c082c634a7..c5de810f22 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Game.Online.API.Requests; using osu.Framework.Bindables; @@ -16,13 +14,13 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu { public partial class PaginatedKudosuHistoryContainer : PaginatedProfileSubsection { - public PaginatedKudosuHistoryContainer(Bindable user) + public PaginatedKudosuHistoryContainer(Bindable user) : base(user, missingText: UsersStrings.ShowExtraKudosuEntryEmpty) { } - protected override APIRequest> CreateRequest(PaginationParameters pagination) - => new GetUserKudosuHistoryRequest(User.Value.Id, pagination); + protected override APIRequest> CreateRequest(APIUser user, PaginationParameters pagination) + => new GetUserKudosuHistoryRequest(user.Id, pagination); protected override Drawable CreateDrawableItem(APIKudosuHistory item) => new DrawableKudosuHistoryItem(item); } diff --git a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs index 26cf78c537..482a853c44 100644 --- a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs +++ b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Game.Overlays.Profile.Sections.Kudosu; using osu.Framework.Localisation; diff --git a/osu.Game/Overlays/Profile/Sections/MedalsSection.cs b/osu.Game/Overlays/Profile/Sections/MedalsSection.cs index a511dc95a0..42f241d662 100644 --- a/osu.Game/Overlays/Profile/Sections/MedalsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/MedalsSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 530391466a..f32221747c 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using System.Threading; @@ -35,20 +33,20 @@ namespace osu.Game.Overlays.Profile.Sections protected virtual int InitialItemsCount => 5; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; protected PaginationParameters? CurrentPage { get; private set; } - protected ReverseChildIDFillFlowContainer ItemsContainer { get; private set; } + protected ReverseChildIDFillFlowContainer ItemsContainer { get; private set; } = null!; - private APIRequest> retrievalRequest; - private CancellationTokenSource loadCancellation; + private APIRequest>? retrievalRequest; + private CancellationTokenSource? loadCancellation; - private ShowMoreButton moreButton; - private OsuSpriteText missing; + private ShowMoreButton moreButton = null!; + private OsuSpriteText missing = null!; private readonly LocalisableString? missingText; - protected PaginatedProfileSubsection(Bindable user, LocalisableString? headerText = null, LocalisableString? missingText = null) + protected PaginatedProfileSubsection(Bindable user, LocalisableString? headerText = null, LocalisableString? missingText = null) : base(user, headerText, CounterVisibilityState.AlwaysVisible) { this.missingText = missingText; @@ -94,7 +92,7 @@ namespace osu.Game.Overlays.Profile.Sections User.BindValueChanged(onUserChanged, true); } - private void onUserChanged(ValueChangedEvent e) + private void onUserChanged(ValueChangedEvent e) { loadCancellation?.Cancel(); retrievalRequest?.Cancel(); @@ -111,17 +109,20 @@ namespace osu.Game.Overlays.Profile.Sections private void showMore() { + if (User.Value == null) + return; + loadCancellation = new CancellationTokenSource(); CurrentPage = CurrentPage?.TakeNext(ItemsPerPage) ?? new PaginationParameters(InitialItemsCount); - retrievalRequest = CreateRequest(CurrentPage.Value); - retrievalRequest.Success += UpdateItems; + retrievalRequest = CreateRequest(User.Value, CurrentPage.Value); + retrievalRequest.Success += items => UpdateItems(items, loadCancellation); api.Queue(retrievalRequest); } - protected virtual void UpdateItems(List items) => Schedule(() => + protected virtual void UpdateItems(List items, CancellationTokenSource cancellationTokenSource) => Schedule(() => { OnItemsReceived(items); @@ -136,7 +137,7 @@ namespace osu.Game.Overlays.Profile.Sections return; } - LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null), drawables => + LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null).Cast(), drawables => { missing.Hide(); @@ -144,7 +145,7 @@ namespace osu.Game.Overlays.Profile.Sections moreButton.IsLoading = false; ItemsContainer.AddRange(drawables); - }, loadCancellation.Token); + }, cancellationTokenSource.Token); }); protected virtual int GetCount(APIUser user) => 0; @@ -153,9 +154,9 @@ namespace osu.Game.Overlays.Profile.Sections { } - protected abstract APIRequest> CreateRequest(PaginationParameters pagination); + protected abstract APIRequest> CreateRequest(APIUser user, PaginationParameters pagination); - protected abstract Drawable CreateDrawableItem(TModel model); + protected abstract Drawable? CreateDrawableItem(TModel model); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs index c81a08fa20..b30faee380 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs index 9d88645c9a..0a0e1a9298 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -1,13 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using JetBrains.Annotations; using osu.Framework.Localisation; using osu.Game.Online.API.Requests.Responses; @@ -15,14 +12,14 @@ namespace osu.Game.Overlays.Profile.Sections { public abstract partial class ProfileSubsection : FillFlowContainer { - protected readonly Bindable User = new Bindable(); + protected readonly Bindable User = new Bindable(); private readonly LocalisableString headerText; private readonly CounterVisibilityState counterVisibilityState; - private ProfileSubsectionHeader header; + private ProfileSubsectionHeader header = null!; - protected ProfileSubsection(Bindable user, LocalisableString? headerText = null, CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + protected ProfileSubsection(Bindable user, LocalisableString? headerText = null, CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) { this.headerText = headerText ?? string.Empty; this.counterVisibilityState = counterVisibilityState; @@ -46,7 +43,6 @@ namespace osu.Game.Overlays.Profile.Sections }; } - [NotNull] protected abstract Drawable CreateContent(); protected void SetCount(int value) => header.Current.Value = value; diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs index 598ac19059..fa21535d02 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; @@ -30,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections private readonly LocalisableString text; private readonly CounterVisibilityState counterState; - private CounterPill counterPill; + private CounterPill counterPill = null!; public ProfileSubsectionHeader(LocalisableString text, CounterVisibilityState counterState) { diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 67df4825cc..0f3e0bc6b2 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -1,12 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -34,10 +32,10 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks protected readonly SoloScoreInfo Score; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; [Resolved] - private OverlayColourProvider colourProvider { get; set; } + private OverlayColourProvider colourProvider { get; set; } = null!; public DrawableProfileScore(SoloScoreInfo score) { @@ -84,7 +82,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(0, 2), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(Score.Beatmap), + new ScoreBeatmapMetadataContainer(Score.Beatmap.AsNonNull()), new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -94,7 +92,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { new OsuSpriteText { - Text = $"{Score.Beatmap?.DifficultyName}", + Text = $"{Score.Beatmap.AsNonNull().DifficultyName}", Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Colour = colours.Yellow }, @@ -199,7 +197,6 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks }); } - [NotNull] protected virtual Drawable CreateRightContent() => CreateDrawableAccuracy(); protected Drawable CreateDrawableAccuracy() => new Container diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index f04d7d9733..6cfe34ec6f 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 38fac075fb..3ef0275f50 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests; using System; @@ -21,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly ScoreType type; - public PaginatedScoreContainer(ScoreType type, Bindable user, LocalisableString headerText) + public PaginatedScoreContainer(ScoreType type, Bindable user, LocalisableString headerText) : base(user, headerText) { this.type = type; @@ -62,8 +60,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks base.OnItemsReceived(items); } - protected override APIRequest> CreateRequest(PaginationParameters pagination) => - new GetUserScoresRequest(User.Value.Id, type, pagination); + protected override APIRequest> CreateRequest(APIUser user, PaginationParameters pagination) => + new GetUserScoresRequest(user.Id, type, pagination); private int drawableItemIndex; diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index ca41bff2f9..ce831b30a8 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Online.API.Requests; using osu.Framework.Localisation; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index c3fa467e5f..0479ab7c16 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -1,10 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -24,14 +23,14 @@ namespace osu.Game.Overlays.Profile.Sections.Recent private const int font_size = 14; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private IRulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } = null!; private readonly APIRecentActivity activity; - private LinkFlowContainer content; + private LinkFlowContainer content = null!; public DrawableRecentActivity(APIRecentActivity activity) { @@ -216,15 +215,15 @@ namespace osu.Game.Overlays.Profile.Sections.Recent rulesets.AvailableRulesets.FirstOrDefault(r => r.ShortName == activity.Mode)?.Name ?? activity.Mode; private void addUserLink() - => content.AddLink(activity.User?.Username, LinkAction.OpenUserProfile, getLinkArgument(activity.User?.Url), creationParameters: t => t.Font = getLinkFont(FontWeight.Bold)); + => content.AddLink(activity.User.AsNonNull().Username, LinkAction.OpenUserProfile, getLinkArgument(activity.User.AsNonNull().Url), creationParameters: t => t.Font = getLinkFont(FontWeight.Bold)); private void addBeatmapLink() - => content.AddLink(activity.Beatmap?.Title, LinkAction.OpenBeatmap, getLinkArgument(activity.Beatmap?.Url), creationParameters: t => t.Font = getLinkFont()); + => content.AddLink(activity.Beatmap.AsNonNull().Title, LinkAction.OpenBeatmap, getLinkArgument(activity.Beatmap.AsNonNull().Url), creationParameters: t => t.Font = getLinkFont()); private void addBeatmapsetLink() - => content.AddLink(activity.Beatmapset?.Title, LinkAction.OpenBeatmapSet, getLinkArgument(activity.Beatmapset?.Url), creationParameters: t => t.Font = getLinkFont()); + => content.AddLink(activity.Beatmapset.AsNonNull().Title, LinkAction.OpenBeatmapSet, getLinkArgument(activity.Beatmapset.AsNonNull().Url), creationParameters: t => t.Font = getLinkFont()); - private string getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.WebsiteRootUrl}{url}").Argument.ToString(); + private string getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.WebsiteRootUrl}{url}").Argument.ToString().AsNonNull(); private FontUsage getLinkFont(FontWeight fontWeight = FontWeight.Regular) => OsuFont.GetFont(size: font_size, weight: fontWeight, italics: true); diff --git a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs index 5eeed24469..6cb439ab33 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index b07dfc154f..4e5b11d79f 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Game.Online.API.Requests; using osu.Framework.Bindables; @@ -18,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent { public partial class PaginatedRecentActivityContainer : PaginatedProfileSubsection { - public PaginatedRecentActivityContainer(Bindable user) + public PaginatedRecentActivityContainer(Bindable user) : base(user, missingText: EventsStrings.Empty) { } @@ -29,8 +27,8 @@ namespace osu.Game.Overlays.Profile.Sections.Recent ItemsContainer.Spacing = new Vector2(0, 8); } - protected override APIRequest> CreateRequest(PaginationParameters pagination) => - new GetUserRecentActivitiesRequest(User.Value.Id, pagination); + protected override APIRequest> CreateRequest(APIUser user, PaginationParameters pagination) => + new GetUserRecentActivitiesRequest(user.Id, pagination); protected override Drawable CreateDrawableItem(APIRecentActivity model) => new DrawableRecentActivity(model); } diff --git a/osu.Game/Overlays/Profile/Sections/RecentSection.cs b/osu.Game/Overlays/Profile/Sections/RecentSection.cs index f2ebf81504..e29dc7f635 100644 --- a/osu.Game/Overlays/Profile/Sections/RecentSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RecentSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; using osu.Game.Overlays.Profile.Sections.Recent; using osu.Game.Resources.Localisation.Web; diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index fa9cbe0449..63cdbea5a4 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -26,12 +23,12 @@ namespace osu.Game.Overlays.Profile /// /// Type of data to be used for X-axis of the graph. /// Type of data to be used for Y-axis of the graph. - public abstract partial class UserGraph : Container, IHasCustomTooltip + public abstract partial class UserGraph : Container, IHasCustomTooltip { protected const float FADE_DURATION = 150; private readonly UserLineGraph graph; - private KeyValuePair[] data; + private KeyValuePair[]? data; private int hoveredIndex = -1; protected UserGraph() @@ -83,8 +80,7 @@ namespace osu.Game.Overlays.Profile /// /// Set of values which will be used to create a graph. /// - [CanBeNull] - protected KeyValuePair[] Data + protected KeyValuePair[]? Data { set { @@ -120,9 +116,9 @@ namespace osu.Game.Overlays.Profile protected virtual void ShowGraph() => graph.FadeIn(FADE_DURATION, Easing.Out); protected virtual void HideGraph() => graph.FadeOut(FADE_DURATION, Easing.Out); - public ITooltip GetCustomTooltip() => new UserGraphTooltip(); + public ITooltip GetCustomTooltip() => new UserGraphTooltip(); - public UserGraphTooltipContent TooltipContent + public UserGraphTooltipContent? TooltipContent { get { @@ -143,7 +139,7 @@ namespace osu.Game.Overlays.Profile private readonly Box ballBg; private readonly Box line; - public Action OnBallMove; + public Action? OnBallMove; public UserLineGraph() { @@ -191,7 +187,7 @@ namespace osu.Game.Overlays.Profile Vector2 position = calculateBallPosition(index); movingBall.MoveToY(position.Y, duration, Easing.OutQuint); bar.MoveToX(position.X, duration, Easing.OutQuint); - OnBallMove.Invoke(index); + OnBallMove?.Invoke(index); } public void ShowBar() => bar.FadeIn(FADE_DURATION); @@ -207,7 +203,7 @@ namespace osu.Game.Overlays.Profile } } - private partial class UserGraphTooltip : VisibilityContainer, ITooltip + private partial class UserGraphTooltip : VisibilityContainer, ITooltip { protected readonly OsuSpriteText Label, Counter, BottomText; private readonly Box background; @@ -267,8 +263,11 @@ namespace osu.Game.Overlays.Profile background.Colour = colours.Gray1; } - public void SetContent(UserGraphTooltipContent content) + public void SetContent(UserGraphTooltipContent? content) { + if (content == null) + return; + Label.Text = content.Name; Counter.Text = content.Count; BottomText.Text = content.Time; diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 386f95cf0a..1a7c4173ab 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -24,11 +23,11 @@ namespace osu.Game.Overlays { public partial class UserProfileOverlay : FullscreenOverlay { - private ProfileSection lastSection; - private ProfileSection[] sections; - private GetUserRequest userReq; - private ProfileSectionsContainer sectionsContainer; - private ProfileSectionTabControl tabs; + private ProfileSection? lastSection; + private ProfileSection[]? sections; + private GetUserRequest? userReq; + private ProfileSectionsContainer? sectionsContainer; + private ProfileSectionTabControl? tabs; public const float CONTENT_X_MARGIN = 70; @@ -133,6 +132,8 @@ namespace osu.Game.Overlays private void userLoadComplete(APIUser user) { + Debug.Assert(sections != null && sectionsContainer != null && tabs != null); + Header.User.Value = user; if (user.ProfileOrder != null) diff --git a/osu.Game/Users/UserCoverBackground.cs b/osu.Game/Users/UserCoverBackground.cs index 69a5fba876..de6a306b2a 100644 --- a/osu.Game/Users/UserCoverBackground.cs +++ b/osu.Game/Users/UserCoverBackground.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -17,15 +15,15 @@ using osuTK.Graphics; namespace osu.Game.Users { - public partial class UserCoverBackground : ModelBackedDrawable + public partial class UserCoverBackground : ModelBackedDrawable { - public APIUser User + public APIUser? User { get => Model; set => Model = value; } - protected override Drawable CreateDrawable(APIUser user) => new Cover(user); + protected override Drawable CreateDrawable(APIUser? user) => new Cover(user); protected override double LoadDelay => 300; @@ -40,9 +38,9 @@ namespace osu.Game.Users [LongRunningLoad] private partial class Cover : CompositeDrawable { - private readonly APIUser user; + private readonly APIUser? user; - public Cover(APIUser user) + public Cover(APIUser? user) { this.user = user; From 73f14bd14fb76791d44365d33d782d519a897177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 15:05:04 +0100 Subject: [PATCH 128/138] Enable NRT in user profile overlay test scenes --- osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs | 2 -- osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs | 2 -- .../Visual/Online/TestSceneProfileRulesetSelector.cs | 4 +--- osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs | 2 -- osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs | 4 +--- osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs | 2 -- .../Visual/Online/TestSceneUserProfilePreviousUsernames.cs | 6 ++---- .../Visual/Online/TestSceneUserProfileRecentSection.cs | 2 -- osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs | 2 -- osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs | 2 -- .../UserInterface/TestSceneProfileSubsectionHeader.cs | 4 +--- 11 files changed, 5 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs index d884c0cf14..28f0e6ff9c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs index e753632474..84497245db 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Overlays.Profile.Sections.Kudosu; using System.Collections.Generic; using System; diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs index e81b7a2ac8..a4d8238fa3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Rulesets.Catch; @@ -24,7 +22,7 @@ namespace osu.Game.Tests.Visual.Online public TestSceneProfileRulesetSelector() { ProfileRulesetSelector selector; - var user = new Bindable(); + var user = new Bindable(); Child = selector = new ProfileRulesetSelector { diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs index d93bf059dd..454242270d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 75743d788a..bfd6372e6f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; @@ -20,7 +18,7 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); - private ProfileHeader header; + private ProfileHeader header = null!; [SetUpSteps] public void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 02d01b4a46..35c7e7bf28 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs index fcefb31716..921738d331 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using NUnit.Framework; using osu.Framework.Graphics; @@ -14,7 +12,7 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public partial class TestSceneUserProfilePreviousUsernames : OsuTestScene { - private PreviousUsernames container; + private PreviousUsernames container = null!; [SetUp] public void SetUp() => Schedule(() => @@ -50,7 +48,7 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("Is hidden", () => container.Alpha == 0); } - private static readonly APIUser[] users = + private static readonly APIUser?[] users = { new APIUser { Id = 1, PreviousUsernames = new[] { "username1" } }, new APIUser { Id = 2, PreviousUsernames = new[] { "longusername", "longerusername" } }, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs index f8432118d4..9d0ea80533 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index 6f0ef10911..5249e8694d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs index ef3a677efc..fdbd8a4325 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs index b4b45da133..c51095f360 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Overlays.Profile.Sections; using osu.Framework.Testing; @@ -18,7 +16,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); - private ProfileSubsectionHeader header; + private ProfileSubsectionHeader header = null!; [Test] public void TestHiddenCounter() From 8f7ae0395a082eda458009ccdaf3504675dca9fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Jan 2023 00:55:05 +0800 Subject: [PATCH 129/138] Fix song select leaderboard potentially showing wrong scores on quick beatmap changes Closes #22002. --- .../Select/Leaderboards/BeatmapLeaderboard.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index b8a2eec526..9e8247059d 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -154,10 +154,17 @@ namespace osu.Game.Screens.Select.Leaderboards scoreRetrievalRequest = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); - scoreRetrievalRequest.Success += response => SetScores( - scoreManager.OrderByTotalScore(response.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo))), - response.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo) - ); + scoreRetrievalRequest.Success += response => Schedule(() => + { + // Beatmap may have changed since fetch request. Can't rely on request cancellation due to Schedule inside SetScores. + if (!fetchBeatmapInfo.Equals(BeatmapInfo)) + return; + + SetScores( + scoreManager.OrderByTotalScore(response.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo))), + response.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo) + ); + }); return scoreRetrievalRequest; } From ac85433178f9f61992e6a2239a3e783fca9f76ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Jan 2023 09:44:34 +0800 Subject: [PATCH 130/138] Fix default volume control keys not working when chat textbox is focused Closes #22004. --- osu.Game/Graphics/UserInterface/HistoryTextBox.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index d74a4f2cdb..b6dc1fcc9b 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -45,6 +45,9 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnKeyDown(KeyDownEvent e) { + if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.ShiftPressed) + return false; + switch (e.Key) { case Key.Up: From 4491a5ba9f7c38010600bcc4db4c358cdc6c7c9e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 3 Jan 2023 13:41:08 +0300 Subject: [PATCH 131/138] Fix editor exporting beatmap combo colours in wrong order --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 03c63ff4f2..7e058d755e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -300,7 +300,7 @@ namespace osu.Game.Beatmaps.Formats { var comboColour = colours[i]; - writer.Write(FormattableString.Invariant($"Combo{i}: ")); + writer.Write(FormattableString.Invariant($"Combo{1 + i}: ")); writer.Write(FormattableString.Invariant($"{(byte)(comboColour.R * byte.MaxValue)},")); writer.Write(FormattableString.Invariant($"{(byte)(comboColour.G * byte.MaxValue)},")); writer.Write(FormattableString.Invariant($"{(byte)(comboColour.B * byte.MaxValue)},")); From efbd9ba4b90946909faa4a07024484e38cd15001 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 3 Jan 2023 15:28:05 +0300 Subject: [PATCH 132/138] Fix catcher not moving when fully hidden in "No Scope" mod --- osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs index 19b4a39f97..ddeea51ecb 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -26,6 +26,9 @@ namespace osu.Game.Rulesets.Catch.Mods var catchPlayfield = (CatchPlayfield)playfield; bool shouldAlwaysShowCatcher = IsBreakTime.Value; float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha; + + // AlwaysPresent required for catcher to still act on input when fully hidden. + catchPlayfield.CatcherArea.AlwaysPresent = true; catchPlayfield.CatcherArea.Alpha = (float)Interpolation.Lerp(catchPlayfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(catchPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); } } From 71125afcb45365a09424a7348ef20d51dc2a2392 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 3 Jan 2023 15:43:36 +0300 Subject: [PATCH 133/138] Add test coverage --- .../Mods/TestSceneCatchModNoScope.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs index 50b928d509..c48bf7adc9 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -18,6 +18,36 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods { protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); + [Test] + public void TestAlwaysHidden() + { + CreateModTest(new ModTestData + { + Mod = new CatchModNoScope + { + HiddenComboCount = { Value = 0 }, + }, + Autoplay = true, + PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Fruit + { + X = CatchPlayfield.CENTER_X * 0.5f, + StartTime = 1000, + }, + new Fruit + { + X = CatchPlayfield.CENTER_X * 1.5f, + StartTime = 2000, + } + } + } + }); + } + [Test] public void TestVisibleDuringBreak() { From 96e81e7f41e825e2ed631a97ab212f13ec30b259 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Jan 2023 01:41:21 +0800 Subject: [PATCH 134/138] Switch on NRT and add `IEquatable` to `GetScoresRequest` --- .../Online/API/Requests/GetScoresRequest.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 7d1d26b75d..f2a2daccb5 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -11,10 +9,11 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using System.Text; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Online.API.Requests { - public class GetScoresRequest : APIRequest + public class GetScoresRequest : APIRequest, IEquatable { public const int MAX_SCORES_PER_REQUEST = 50; @@ -23,7 +22,7 @@ namespace osu.Game.Online.API.Requests private readonly IRulesetInfo ruleset; private readonly IEnumerable mods; - public GetScoresRequest(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null) + public GetScoresRequest(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable? mods = null) { if (beatmapInfo.OnlineID <= 0) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(IBeatmapInfo.OnlineID)}."); @@ -51,5 +50,16 @@ namespace osu.Game.Online.API.Requests return query.ToString(); } + + public bool Equals(GetScoresRequest? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return beatmapInfo.Equals(other.beatmapInfo) + && scope == other.scope + && ruleset.Equals(other.ruleset) + && mods.SequenceEqual(other.mods); + } } } From beb3b96aca7366fa812d436103ffbdfa5b682eee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Jan 2023 01:44:00 +0800 Subject: [PATCH 135/138] Harden request equality checks --- .../Select/Leaderboards/BeatmapLeaderboard.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 9e8247059d..c419501add 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -152,12 +152,14 @@ namespace osu.Game.Screens.Select.Leaderboards else if (filterMods) requestMods = mods.Value; - scoreRetrievalRequest = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); + scoreRetrievalRequest?.Cancel(); - scoreRetrievalRequest.Success += response => Schedule(() => + var newRequest = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); + newRequest.Success += response => Schedule(() => { - // Beatmap may have changed since fetch request. Can't rely on request cancellation due to Schedule inside SetScores. - if (!fetchBeatmapInfo.Equals(BeatmapInfo)) + // Request may have changed since fetch request. + // Can't rely on request cancellation due to Schedule inside SetScores so let's play it safe. + if (!newRequest.Equals(scoreRetrievalRequest)) return; SetScores( @@ -166,7 +168,7 @@ namespace osu.Game.Screens.Select.Leaderboards ); }); - return scoreRetrievalRequest; + return scoreRetrievalRequest = newRequest; } protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index, IsOnlineScope) From 49b098407983ec0e39a2b8e19c2df469fe0ba703 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 2 Jan 2023 21:53:58 -0800 Subject: [PATCH 136/138] Add failing playlist no selection after deleted test --- .../TestScenePlaylistsRoomSettingsPlaylist.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index 91e9ce5ea2..ae27db0dd1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -46,10 +47,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("item removed", () => !playlist.Items.Contains(selectedItem)); } - [Test] - public void TestNextItemSelectedAfterDeletion() + [TestCase(true)] + [TestCase(false)] + public void TestNextItemSelectedAfterDeletion(bool allowSelection) { - createPlaylist(); + createPlaylist(p => + { + p.AllowSelection = allowSelection; + }); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -57,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer moveToDeleteButton(0); AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); + AddAssert("item 0 is " + (allowSelection ? "selected" : "not selected"), () => playlist.SelectedItem.Value == (allowSelection ? playlist.Items[0] : null)); } [Test] @@ -117,7 +122,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); }); - private void createPlaylist() + private void createPlaylist(Action setupPlaylist = null) { AddStep("create playlist", () => { @@ -154,6 +159,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); } + + setupPlaylist?.Invoke(playlist); }); AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); From 5dfd4180c84c09bce3ef25803d66ab0168377842 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 2 Jan 2023 21:22:24 -0800 Subject: [PATCH 137/138] Fix playlist selecting random item when not allowed after deleting --- .../OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs index df502ae09c..736f09584b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs @@ -24,7 +24,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Items.Remove(item); - SelectedItem.Value = nextItem ?? Items.LastOrDefault(); + if (AllowSelection && SelectedItem.Value == item) + SelectedItem.Value = nextItem ?? Items.LastOrDefault(); }; } } From 5fb6f220e69437c87224e4ae13c4da6205c1f87d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 2 Jan 2023 21:21:27 -0800 Subject: [PATCH 138/138] Fix playlist items not animating when rearranging --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index f5477837b0..8abdec9ade 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -156,6 +156,8 @@ namespace osu.Game.Screens.OnlinePlay protected override FillFlowContainer> CreateListFillFlowContainer() => new FillFlowContainer> { + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, Spacing = new Vector2(0, 2) };