From ce29d6fd81b9aef105bca0befc77a16ab3fab1e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 11:13:06 +0900 Subject: [PATCH 01/13] Change scroll speed adjust defaults to F3/F4 Not the most obvious keys, but does match stable expectations so let's go with it for now. Rationale for not using +/- is that local audio offset was bound to those, and people will expect it to be when we get to implementing eventually. Also, adjust the scroll speed is a pretty rare thing to do since it's been constant across beatmaps for years now. --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 9fd7caadd0..22446634c1 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -84,8 +84,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.ExtraMouseButton2, GlobalAction.SkipCutscene), new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry), new KeyBinding(new[] { InputKey.Control, InputKey.Tilde }, GlobalAction.QuickExit), - new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), - new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), + new KeyBinding(new[] { InputKey.F3 }, GlobalAction.DecreaseScrollSpeed), + new KeyBinding(new[] { InputKey.F4 }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), new KeyBinding(InputKey.Space, GlobalAction.TogglePauseReplay), From 8d21f0b04b430e9c0943213a64b59cdc71ea3e2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 11:14:22 +0900 Subject: [PATCH 02/13] Add migration to reset conflicting scroll speed key bindings --- osu.Game/Database/RealmContextFactory.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 013a2e9d64..40b67beed5 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -6,9 +6,11 @@ using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Development; +using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; +using osu.Game.Input.Bindings; using osu.Game.Models; using Realms; @@ -32,8 +34,9 @@ namespace osu.Game.Database /// Version history: /// 6 First tracked version (~20211018) /// 7 Changed OnlineID fields to non-nullable to add indexing support (20211018) + /// 8 Rebind scroll adjust keys to not have control modifier (20211029) /// - private const int schema_version = 7; + private const int schema_version = 8; /// /// Lock object which is held during sections, blocking context creation during blocking periods. @@ -148,6 +151,21 @@ namespace osu.Game.Database private void onMigration(Migration migration, ulong lastSchemaVersion) { + if (lastSchemaVersion < 8) + { + // Ctrl -/+ now adjusts UI scale so let's clear any bindings which overlap these combinations. + // New defaults will be populated by the key store afterwards. + var keyBindings = migration.NewRealm.All(); + + var increaseSpeedBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.IncreaseScrollSpeed); + if (increaseSpeedBinding != null && increaseSpeedBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.Plus })) + migration.NewRealm.Remove(increaseSpeedBinding); + + var descreaseSpeedBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.DecreaseScrollSpeed); + if (descreaseSpeedBinding != null && descreaseSpeedBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.Minus })) + migration.NewRealm.Remove(descreaseSpeedBinding); + } + if (lastSchemaVersion < 7) { convertOnlineIDs(); From 3838fe5c6adc2d84964e9aebb934edbed40dd02b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 11:39:15 +0900 Subject: [PATCH 03/13] Fix typo in variable name --- osu.Game/Database/RealmContextFactory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 40b67beed5..85f7dd6b89 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -161,9 +161,9 @@ namespace osu.Game.Database if (increaseSpeedBinding != null && increaseSpeedBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.Plus })) migration.NewRealm.Remove(increaseSpeedBinding); - var descreaseSpeedBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.DecreaseScrollSpeed); - if (descreaseSpeedBinding != null && descreaseSpeedBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.Minus })) - migration.NewRealm.Remove(descreaseSpeedBinding); + var decreaseSpeedBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.DecreaseScrollSpeed); + if (decreaseSpeedBinding != null && decreaseSpeedBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.Minus })) + migration.NewRealm.Remove(decreaseSpeedBinding); } if (lastSchemaVersion < 7) From 6f863ca204cd9488a4e9a141ce8feadbbabef26d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Aug 2021 17:12:04 +0200 Subject: [PATCH 04/13] Adjust game-side text flow containers to part-based model --- .../Graphics/Containers/LinkFlowContainer.cs | 50 ++++++++++++++----- .../Containers/OsuTextFlowContainer.cs | 6 +-- osu.Game/Graphics/ErrorTextFlowContainer.cs | 9 ++-- osu.Game/Online/Chat/DrawableLinkCompiler.cs | 7 +++ .../Overlays/AccountCreation/ScreenEntry.cs | 5 +- .../Changelog/ChangelogSupporterPromo.cs | 7 ++- osu.Game/Overlays/Music/PlaylistItem.cs | 16 +++--- osu.Game/Screens/Menu/Disclaimer.cs | 10 ++-- 8 files changed, 73 insertions(+), 37 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 21c1d70d45..7569200c53 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites; using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Graphics.Sprites; @@ -71,26 +72,20 @@ namespace osu.Game.Graphics.Containers var spriteText = new OsuSpriteText { Text = text }; AddText(spriteText, creationParameters); - createLink(spriteText.Yield(), new LinkDetails(action, argument), tooltipText); + createLink(new TextPartManual(spriteText.Yield()), new LinkDetails(action, argument), tooltipText); } public void AddLink(IEnumerable text, LinkAction action, string linkArgument, string tooltipText = null) { - foreach (var t in text) - AddArbitraryDrawable(t); - - createLink(text, new LinkDetails(action, linkArgument), tooltipText); + createLink(new TextPartManual(text), new LinkDetails(action, linkArgument), tooltipText); } public void AddUserLink(User user, Action creationParameters = null) => createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "view profile"); - private void createLink(IEnumerable drawables, LinkDetails link, string tooltipText, Action action = null) + private void createLink(ITextPart textPart, LinkDetails link, LocalisableString tooltipText, Action action = null) { - var linkCompiler = CreateLinkCompiler(drawables.OfType()); - linkCompiler.RelativeSizeAxes = Axes.Both; - linkCompiler.TooltipText = tooltipText; - linkCompiler.Action = () => + Action onClickAction = () => { if (action != null) action(); @@ -101,10 +96,41 @@ namespace osu.Game.Graphics.Containers host.OpenUrlExternally(link.Argument); }; - AddInternal(linkCompiler); + AddPart(new TextLink(textPart, tooltipText, onClickAction)); } - protected virtual DrawableLinkCompiler CreateLinkCompiler(IEnumerable parts) => new DrawableLinkCompiler(parts); + private class TextLink : TextPart + { + private readonly ITextPart innerPart; + private readonly LocalisableString tooltipText; + private readonly Action action; + + public TextLink(ITextPart innerPart, LocalisableString tooltipText, Action action) + { + this.innerPart = innerPart; + this.tooltipText = tooltipText; + this.action = action; + } + + protected override IEnumerable CreateDrawablesFor(TextFlowContainer textFlowContainer) + { + var linkFlowContainer = (LinkFlowContainer)textFlowContainer; + + innerPart.RecreateDrawablesFor(linkFlowContainer); + var drawables = innerPart.Drawables.ToList(); + + drawables.Add(linkFlowContainer.CreateLinkCompiler(innerPart).With(c => + { + c.RelativeSizeAxes = Axes.Both; + c.TooltipText = tooltipText; + c.Action = action; + })); + + return drawables; + } + } + + protected virtual DrawableLinkCompiler CreateLinkCompiler(ITextPart textPart) => new DrawableLinkCompiler(textPart); // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. // However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation. diff --git a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs index 6a87a4b8b9..b8237832a3 100644 --- a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -19,8 +19,8 @@ namespace osu.Game.Graphics.Containers protected override SpriteText CreateSpriteText() => new OsuSpriteText(); - public void AddArbitraryDrawable(Drawable drawable) => AddInternal(drawable); + public ITextPart AddArbitraryDrawable(Drawable drawable) => AddPart(new TextPartManual(drawable.Yield())); - public IEnumerable AddIcon(IconUsage icon, Action creationParameters = null) => AddText(icon.Icon.ToString(), creationParameters); + public ITextPart AddIcon(IconUsage icon, Action creationParameters = null) => AddText(icon.Icon.ToString(), creationParameters); } } diff --git a/osu.Game/Graphics/ErrorTextFlowContainer.cs b/osu.Game/Graphics/ErrorTextFlowContainer.cs index 486382bf33..dafc363973 100644 --- a/osu.Game/Graphics/ErrorTextFlowContainer.cs +++ b/osu.Game/Graphics/ErrorTextFlowContainer.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osuTK.Graphics; @@ -10,7 +10,7 @@ namespace osu.Game.Graphics { public class ErrorTextFlowContainer : OsuTextFlowContainer { - private readonly List errorDrawables = new List(); + private readonly List errorTextParts = new List(); public ErrorTextFlowContainer() : base(cp => cp.Font = cp.Font.With(size: 12)) @@ -19,7 +19,8 @@ namespace osu.Game.Graphics public void ClearErrors() { - errorDrawables.ForEach(d => d.Expire()); + foreach (var textPart in errorTextParts) + RemovePart(textPart); } public void AddErrors(string[] errors) @@ -29,7 +30,7 @@ namespace osu.Game.Graphics if (errors == null) return; foreach (string error in errors) - errorDrawables.AddRange(AddParagraph(error, cp => cp.Colour = Color4.Red)); + errorTextParts.Add(AddParagraph(error, cp => cp.Colour = Color4.Red)); } } } diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index 4df60eba69..8356b36667 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -30,6 +32,11 @@ namespace osu.Game.Online.Chat protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); + public DrawableLinkCompiler(ITextPart part) + : this(part.Drawables.OfType()) + { + } + public DrawableLinkCompiler(IEnumerable parts) : base(HoverSampleSet.Submit) { diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index bcb3d4b635..8ee3b1cb2e 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -37,7 +36,7 @@ namespace osu.Game.Overlays.AccountCreation private IAPIProvider api { get; set; } private ShakeContainer registerShake; - private IEnumerable characterCheckText; + private ITextPart characterCheckText; private OsuTextBox[] textboxes; private LoadingLayer loadingLayer; @@ -136,7 +135,7 @@ namespace osu.Game.Overlays.AccountCreation characterCheckText = passwordDescription.AddText("8 characters long"); passwordDescription.AddText(". Choose something long but also something you will remember, like a line from your favourite song."); - passwordTextBox.Current.ValueChanged += password => { characterCheckText.ForEach(s => s.Colour = password.NewValue.Length == 0 ? Color4.White : Interpolation.ValueAt(password.NewValue.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In)); }; + passwordTextBox.Current.ValueChanged += password => { characterCheckText.Drawables.ForEach(s => s.Colour = password.NewValue.Length == 0 ? Color4.White : Interpolation.ValueAt(password.NewValue.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In)); }; } public override void OnEntering(IScreen last) diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs index 1b0a62dc4a..5cc598ae70 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -166,12 +165,12 @@ namespace osu.Game.Overlays.Changelog { } - protected override DrawableLinkCompiler CreateLinkCompiler(IEnumerable parts) => new SupporterPromoLinkCompiler(parts); + protected override DrawableLinkCompiler CreateLinkCompiler(ITextPart textPart) => new SupporterPromoLinkCompiler(textPart); private class SupporterPromoLinkCompiler : DrawableLinkCompiler { - public SupporterPromoLinkCompiler(IEnumerable parts) - : base(parts) + public SupporterPromoLinkCompiler(ITextPart part) + : base(part) { } diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index ef25de77c6..eea2a9dc7e 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -3,12 +3,10 @@ using System; using System.Collections.Generic; -using System.Linq; 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.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; @@ -25,7 +23,7 @@ namespace osu.Game.Overlays.Music public Action RequestSelection; private TextFlowContainer text; - private IEnumerable titleSprites; + private ITextPart titlePart; private ILocalisedBindableString title; private ILocalisedBindableString artist; @@ -63,11 +61,16 @@ namespace osu.Game.Overlays.Music if (set.OldValue?.Equals(Model) != true && set.NewValue?.Equals(Model) != true) return; - foreach (Drawable s in titleSprites) - s.FadeColour(set.NewValue.Equals(Model) ? selectedColour : Color4.White, FADE_DURATION); + updateSelectionState(false); }, true); } + private void updateSelectionState(bool instant) + { + foreach (Drawable s in titlePart.Drawables) + s.FadeColour(SelectedSet.Value?.Equals(Model) == true ? selectedColour : Color4.White, instant ? 0 : FADE_DURATION); + } + protected override Drawable CreateContent() => text = new OsuTextFlowContainer { RelativeSizeAxes = Axes.X, @@ -79,7 +82,8 @@ namespace osu.Game.Overlays.Music text.Clear(); // space after the title to put a space between the title and artist - titleSprites = text.AddText(title.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)).OfType(); + titlePart = text.AddText(title.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); + updateSelectionState(true); text.AddText(artist.Value, sprite => { diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 7f34e1e395..b8abc131fd 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Menu private readonly Bindable currentUser = new Bindable(); private FillFlowContainer fill; - private readonly List expendableText = new List(); + private readonly List expendableText = new List(); public Disclaimer(OsuScreen nextScreen = null) { @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Menu textFlow.AddText("this is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular)); - expendableText.AddRange(textFlow.AddText("lazer", t => + expendableText.Add(textFlow.AddText("lazer", t => { t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular); t.Colour = colours.PinkLight; @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Menu t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold); t.Colour = colours.Pink; }); - expendableText.AddRange(textFlow.AddText(" coming to osu!", formatRegular)); + expendableText.Add(textFlow.AddText(" coming to osu!", formatRegular)); textFlow.AddText(".", formatRegular); textFlow.NewParagraph(); @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Menu t.Font = t.Font.With(size: 20); t.Origin = Anchor.Centre; t.Colour = colours.Pink; - }).First(); + }).Drawables.First(); if (IsLoaded) animateHeart(); @@ -193,7 +193,7 @@ namespace osu.Game.Screens.Menu using (BeginDelayedSequence(520 + 160)) { fill.MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart); - Schedule(() => expendableText.ForEach(t => + Schedule(() => expendableText.SelectMany(t => t.Drawables).ForEach(t => { t.FadeOut(100); t.ScaleTo(new Vector2(0, 1), 100, Easing.OutQuart); From 61f0339c1d43bac24a1b1addf9d66859fba62401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 Aug 2021 20:52:12 +0200 Subject: [PATCH 05/13] Avoid double-adding chunks in link flow container --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 7569200c53..0b43c16ebe 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -59,19 +59,20 @@ namespace osu.Game.Graphics.Containers } public void AddLink(string text, string url, Action creationParameters = null) => - createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), url); + createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.External, url), url); public void AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, string.Empty), tooltipText, action); + => createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.Custom, string.Empty), tooltipText, action); public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), tooltipText); + => createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(action, argument), tooltipText); public void AddLink(LocalisableString text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) { var spriteText = new OsuSpriteText { Text = text }; AddText(spriteText, creationParameters); + RemoveInternal(spriteText); // TODO: temporary, will go away when TextParts support localisation properly. createLink(new TextPartManual(spriteText.Yield()), new LinkDetails(action, argument), tooltipText); } @@ -81,7 +82,7 @@ namespace osu.Game.Graphics.Containers } public void AddUserLink(User user, Action creationParameters = null) - => createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "view profile"); + => createLink(CreateChunkFor(user.Username, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "view profile"); private void createLink(ITextPart textPart, LinkDetails link, LocalisableString tooltipText, Action action = null) { From 7583435901388816aaf9cf6000977b627a429e6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 16:45:10 +0900 Subject: [PATCH 06/13] Refactor `BeatmapDifficultyCache` to work with `IBeatmapInfo` --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 47 +++++++++++---------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 3777365088..9a0cdb387d 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -88,7 +88,7 @@ namespace osu.Game.Beatmaps /// The to get the difficulty of. /// An optional which stops updating the star difficulty for the given . /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state (but not during updates to ruleset and mods if a stale value is already propagated). - public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) + public IBindable GetBindableDifficulty([NotNull] IBeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) { var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken); @@ -99,42 +99,45 @@ namespace osu.Game.Beatmaps } /// - /// Retrieves a bindable containing the star difficulty of a with a given and combination. + /// Retrieves a bindable containing the star difficulty of a with a given and combination. /// /// /// The bindable will not update to follow the currently-selected ruleset and mods or its settings. /// - /// The to get the difficulty of. - /// The to get the difficulty with. If null, the 's ruleset is used. + /// The to get the difficulty of. + /// The to get the difficulty with. If null, the 's ruleset is used. /// The s to get the difficulty with. If null, no mods will be assumed. - /// An optional which stops updating the star difficulty for the given . + /// An optional which stops updating the star difficulty for the given . /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state. - public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, + public IBindable GetBindableDifficulty([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, CancellationToken cancellationToken = default) => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken); /// - /// Retrieves the difficulty of a . + /// Retrieves the difficulty of a . /// - /// The to get the difficulty of. - /// The to get the difficulty with. + /// The to get the difficulty of. + /// The to get the difficulty with. /// The s to get the difficulty with. /// An optional which stops computing the star difficulty. /// The . - public virtual Task GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, + public virtual Task GetDifficultyAsync([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo = null, [CanBeNull] IEnumerable mods = null, CancellationToken cancellationToken = default) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. rulesetInfo ??= beatmapInfo.Ruleset; + var localBeatmapInfo = beatmapInfo as BeatmapInfo; + var localRulesetInfo = rulesetInfo as RulesetInfo; + // Difficulty can only be computed if the beatmap and ruleset are locally available. - if (beatmapInfo.ID == 0 || rulesetInfo.ID == null) + if (localBeatmapInfo == null || localRulesetInfo == null) { // If not, fall back to the existing star difficulty (e.g. from an online source). - return Task.FromResult(new StarDifficulty(beatmapInfo.StarDifficulty, beatmapInfo.MaxCombo ?? 0)); + return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0)); } - return GetAsync(new DifficultyCacheLookup(beatmapInfo, rulesetInfo, mods), cancellationToken); + return GetAsync(new DifficultyCacheLookup(localBeatmapInfo, localRulesetInfo, mods), cancellationToken); } protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default) @@ -227,12 +230,12 @@ namespace osu.Game.Beatmaps /// /// Creates a new and triggers an initial value update. /// - /// The that star difficulty should correspond to. - /// The initial to get the difficulty with. + /// The that star difficulty should correspond to. + /// The initial to get the difficulty with. /// The initial s to get the difficulty with. - /// An optional which stops updating the star difficulty for the given . + /// An optional which stops updating the star difficulty for the given . /// The . - private BindableStarDifficulty createBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo initialRulesetInfo, [CanBeNull] IEnumerable initialMods, + private BindableStarDifficulty createBindable([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo initialRulesetInfo, [CanBeNull] IEnumerable initialMods, CancellationToken cancellationToken) { var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken); @@ -244,12 +247,12 @@ namespace osu.Game.Beatmaps /// Updates the value of a with a given ruleset + mods. /// /// The to update. - /// The to update with. + /// The to update with. /// The s to update with. /// A token that may be used to cancel this update. - private void updateBindable([NotNull] BindableStarDifficulty bindable, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, CancellationToken cancellationToken = default) + private void updateBindable([NotNull] BindableStarDifficulty bindable, [CanBeNull] IRulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, CancellationToken cancellationToken = default) { - // GetDifficultyAsync will fall back to existing data from BeatmapInfo if not locally available + // GetDifficultyAsync will fall back to existing data from IBeatmapInfo if not locally available // (contrary to GetAsync) GetDifficultyAsync(bindable.BeatmapInfo, rulesetInfo, mods, cancellationToken) .ContinueWith(t => @@ -343,10 +346,10 @@ namespace osu.Game.Beatmaps private class BindableStarDifficulty : Bindable { - public readonly BeatmapInfo BeatmapInfo; + public readonly IBeatmapInfo BeatmapInfo; public readonly CancellationToken CancellationToken; - public BindableStarDifficulty(BeatmapInfo beatmapInfo, CancellationToken cancellationToken) + public BindableStarDifficulty(IBeatmapInfo beatmapInfo, CancellationToken cancellationToken) { BeatmapInfo = beatmapInfo; CancellationToken = cancellationToken; From 3598adb3449e395bc15ce29d2e895a5421c84705 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 17:04:56 +0900 Subject: [PATCH 07/13] Fix test implementing old version of class --- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index f91d3f595b..50ae673c06 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.SongSelect } } - public override async Task GetDifficultyAsync(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo = null, IEnumerable mods = null, CancellationToken cancellationToken = default) + public override async Task GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo rulesetInfo = null, IEnumerable mods = null, CancellationToken cancellationToken = default) { if (blockCalculation) await calculationBlocker.Task.ConfigureAwait(false); From be0564f7326960959970c398203bac1e8ed4280f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 17:03:50 +0900 Subject: [PATCH 08/13] Update `DifficultyIcon` classes to use `IBeatmapInfo` --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 23 +++++++++++-------- .../Drawables/DifficultyIconTooltip.cs | 6 ++--- .../Drawables/GroupedDifficultyIcon.cs | 6 ++--- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 880d70aec2..3a55b9261f 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -37,10 +37,10 @@ namespace osu.Game.Beatmaps.Drawables } [NotNull] - private readonly BeatmapInfo beatmapInfo; + private readonly IBeatmapInfo beatmapInfo; [CanBeNull] - private readonly RulesetInfo ruleset; + private readonly IRulesetInfo ruleset; [CanBeNull] private readonly IReadOnlyList mods; @@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps.Drawables /// The ruleset to show the difficulty with. /// The mods to show the difficulty with. /// Whether to display a tooltip when hovered. - public DifficultyIcon([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true) + public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true) : this(beatmapInfo, shouldShowTooltip) { this.ruleset = ruleset ?? beatmapInfo.Ruleset; @@ -73,7 +73,7 @@ namespace osu.Game.Beatmaps.Drawables /// The beatmap to show the difficulty of. /// Whether to display a tooltip when hovered. /// Whether to perform difficulty lookup (including calculation if necessary). - public DifficultyIcon([NotNull] BeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) + public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) { this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo)); this.shouldShowTooltip = shouldShowTooltip; @@ -84,6 +84,9 @@ namespace osu.Game.Beatmaps.Drawables InternalChild = iconContainer = new Container { Size = new Vector2(20f) }; } + [Resolved] + private RulesetStore rulesets { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -105,7 +108,7 @@ namespace osu.Game.Beatmaps.Drawables Child = background = new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.ForStarDifficulty(beatmapInfo.StarDifficulty) // Default value that will be re-populated once difficulty calculation completes + Colour = colours.ForStarDifficulty(beatmapInfo.StarRating) // Default value that will be re-populated once difficulty calculation completes }, }, new ConstrainedIconContainer @@ -114,14 +117,14 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) - Icon = (ruleset ?? beatmapInfo.Ruleset)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } + Icon = rulesets.GetRuleset((ruleset ?? beatmapInfo.Ruleset).OnlineID)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } }, }; if (performBackgroundDifficultyLookup) iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0)); else - difficultyBindable.Value = new StarDifficulty(beatmapInfo.StarDifficulty, 0); + difficultyBindable.Value = new StarDifficulty(beatmapInfo.StarRating, 0); difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars)); } @@ -134,8 +137,8 @@ namespace osu.Game.Beatmaps.Drawables { public readonly Bindable StarDifficulty = new Bindable(); - private readonly BeatmapInfo beatmapInfo; - private readonly RulesetInfo ruleset; + private readonly IBeatmapInfo beatmapInfo; + private readonly IRulesetInfo ruleset; private readonly IReadOnlyList mods; private CancellationTokenSource difficultyCancellation; @@ -143,7 +146,7 @@ namespace osu.Game.Beatmaps.Drawables [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } - public DifficultyRetriever(BeatmapInfo beatmapInfo, RulesetInfo ruleset, IReadOnlyList mods) + public DifficultyRetriever(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, IReadOnlyList mods) { this.beatmapInfo = beatmapInfo; this.ruleset = ruleset; diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index d4c9f83a0a..ec4bcbd65f 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -89,7 +89,7 @@ namespace osu.Game.Beatmaps.Drawables public void SetContent(DifficultyIconTooltipContent content) { - difficultyName.Text = content.BeatmapInfo.Version; + difficultyName.Text = content.BeatmapInfo.DifficultyName; starDifficulty.UnbindAll(); starDifficulty.BindTo(content.Difficulty); @@ -109,10 +109,10 @@ namespace osu.Game.Beatmaps.Drawables internal class DifficultyIconTooltipContent { - public readonly BeatmapInfo BeatmapInfo; + public readonly IBeatmapInfo BeatmapInfo; public readonly IBindable Difficulty; - public DifficultyIconTooltipContent(BeatmapInfo beatmapInfo, IBindable difficulty) + public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable difficulty) { BeatmapInfo = beatmapInfo; Difficulty = difficulty; diff --git a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs index fcee4c2f1a..799a02579e 100644 --- a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs @@ -19,8 +19,8 @@ namespace osu.Game.Beatmaps.Drawables /// public class GroupedDifficultyIcon : DifficultyIcon { - public GroupedDifficultyIcon(List beatmaps, RulesetInfo ruleset, Color4 counterColour) - : base(beatmaps.OrderBy(b => b.StarDifficulty).Last(), ruleset, null, false) + public GroupedDifficultyIcon(IEnumerable beatmaps, IRulesetInfo ruleset, Color4 counterColour) + : base(beatmaps.OrderBy(b => b.StarRating).Last(), ruleset, null, false) { AddInternal(new OsuSpriteText { @@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps.Drawables Padding = new MarginPadding { Left = Size.X }, Margin = new MarginPadding { Left = 2, Right = 5 }, Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), - Text = beatmaps.Count.ToString(), + Text = beatmaps.Count().ToString(), Colour = counterColour, }); } From 7db8bdfb7c01db278088b8aa45c963eef6fad5ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 18:16:53 +0900 Subject: [PATCH 09/13] Fix fallback logic not considering case where ruleset is not available Occurs only in tests. --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 3a55b9261f..6d1984db5b 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -117,7 +117,7 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) - Icon = rulesets.GetRuleset((ruleset ?? beatmapInfo.Ruleset).OnlineID)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } + Icon = getRulesetIcon() }, }; @@ -129,6 +129,16 @@ namespace osu.Game.Beatmaps.Drawables difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars)); } + private Drawable getRulesetIcon() + { + int? onlineID = (ruleset ?? beatmapInfo.Ruleset)?.OnlineID; + + if (onlineID >= 0 && rulesets.GetRuleset(onlineID.Value)?.CreateInstance() is Ruleset rulesetInstance) + return rulesetInstance.CreateIcon(); + + return new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }; + } + ITooltip IHasCustomTooltip.GetCustomTooltip() => new DifficultyIconTooltip(); DifficultyIconTooltipContent IHasCustomTooltip.TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmapInfo, difficultyBindable) : null; From 07e3ced315fe457eb97e32879c0c061541e201f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 18:22:23 +0900 Subject: [PATCH 10/13] Fix test scene and remove "impossible" nullable coalesce --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 ++ osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 66f15670f5..1eda3be039 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -15,6 +15,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; @@ -868,6 +869,7 @@ namespace osu.Game.Tests.Visual.SongSelect OnlineBeatmapID = id++ * 10, Version = version, StarDifficulty = diff, + Ruleset = new OsuRuleset().RulesetInfo, BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = diff, diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 6d1984db5b..64412675bb 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -131,7 +131,7 @@ namespace osu.Game.Beatmaps.Drawables private Drawable getRulesetIcon() { - int? onlineID = (ruleset ?? beatmapInfo.Ruleset)?.OnlineID; + int? onlineID = (ruleset ?? beatmapInfo.Ruleset).OnlineID; if (onlineID >= 0 && rulesets.GetRuleset(onlineID.Value)?.CreateInstance() is Ruleset rulesetInstance) return rulesetInstance.CreateIcon(); From 04acc7601c2a96906fb6e462391df4502ee61015 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 18:35:15 +0900 Subject: [PATCH 11/13] Fix one more missed case --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 1eda3be039..7a38d213d9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -685,6 +685,7 @@ namespace osu.Game.Tests.Visual.SongSelect set.Beatmaps.Add(new BeatmapInfo { Version = $"Stars: {i}", + Ruleset = new OsuRuleset().RulesetInfo, StarDifficulty = i, }); } From 1fd05ab78aa7b09c63c8040e19fd92420ad02fc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 18:56:13 +0900 Subject: [PATCH 12/13] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 552675d706..dec994bcb2 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8ba6e41d53..8052ab5254 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index e55dbb3bfe..d152cb7066 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 6e4f7af8d36dbf59d396fec4d1ccdcbed9a0ea45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 30 Oct 2021 14:15:20 +0200 Subject: [PATCH 13/13] Mark `IHasOnlineID` implementation with region --- osu.Game/Scoring/ScoreInfo.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 8eaec0a477..ed3b27dd22 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -272,6 +272,10 @@ namespace osu.Game.Scoring return ReferenceEquals(this, other); } + #region Implementation of IHasOnlineID + public long OnlineID => OnlineScoreID ?? -1; + + #endregion } }