From 6e812ebd566c1598303fb2e7ccf5a3cd13f4458e Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 16 Dec 2019 00:45:33 +0100 Subject: [PATCH 001/520] Reimplement chat settings from stable --- osu.Game/Configuration/OsuConfigManager.cs | 12 ++++++- .../Online/AlertsAndPrivacySettings.cs | 32 +++++++++++++++++++ .../Sections/Online/InGameChatSettings.cs | 29 +++++++++++++++++ .../Settings/Sections/OnlineSection.cs | 4 ++- 4 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs create mode 100644 osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index e26021d930..ed562637d4 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -49,6 +49,12 @@ namespace osu.Game.Configuration Set(OsuSetting.ExternalLinkWarning, true); + Set(OsuSetting.ChatHighlightName, true); + Set(OsuSetting.ChatMessageNotification, true); + + Set(OsuSetting.HighlightWords, string.Empty); + Set(OsuSetting.IgnoreList, string.Empty); + // Audio Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); @@ -180,6 +186,10 @@ namespace osu.Game.Configuration ScalingSizeX, ScalingSizeY, UIScale, - IntroSequence + IntroSequence, + ChatHighlightName, + ChatMessageNotification, + HighlightWords, + IgnoreList } } diff --git a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs new file mode 100644 index 0000000000..d84bf4eb3f --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs @@ -0,0 +1,32 @@ +// 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.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Online +{ + public class AlertsAndPrivacySettings : SettingsSubsection + { + protected override string Header => "Alerts and Privacy"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Show a notification popup when someone says your name", + Bindable = config.GetBindable(OsuSetting.ChatHighlightName) + }, + new SettingsCheckbox + { + LabelText = "Show chat message notifications", + Bindable = config.GetBindable(OsuSetting.ChatMessageNotification) + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs new file mode 100644 index 0000000000..e9cb1477ad --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs @@ -0,0 +1,29 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Online +{ + public class InGameChatSettings : SettingsSubsection + { + protected override string Header => "In-Game Chat"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new Drawable[] + { + new SettingsTextBox + { + LabelText = "Chat ignore list (space-separated list)", + Bindable = config.GetBindable(OsuSetting.IgnoreList) + }, + new SettingsTextBox + { + LabelText = "Chat highlight words (space-separated list)", + Bindable = config.GetBindable(OsuSetting.HighlightWords) + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index 80295690c0..67a2e881d0 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs @@ -16,7 +16,9 @@ namespace osu.Game.Overlays.Settings.Sections { Children = new Drawable[] { - new WebSettings() + new WebSettings(), + new AlertsAndPrivacySettings(), + new InGameChatSettings() }; } } From e8180ab153901844621d0877918dd918a43c9c73 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 16 Dec 2019 00:45:55 +0100 Subject: [PATCH 002/520] Add ToString() method to message for better debugging --- osu.Game/Online/Chat/Message.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 2e41038a59..3b0507eb0c 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -63,5 +63,7 @@ namespace osu.Game.Online.Chat // ReSharper disable once ImpureMethodCallOnReadonlyValueField public override int GetHashCode() => Id.GetHashCode(); + + public override string ToString() => $"[{ChannelId}] ({Id}) {Sender}: {Content}"; } } From 8dfc8929f11ed8b4be09bca362ce8e6bf83ad62b Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 16 Dec 2019 00:48:22 +0100 Subject: [PATCH 003/520] Add chat and notification logic to DrawableChannel with alongside multiple helper methods --- osu.Game/Overlays/Chat/DrawableChannel.cs | 167 +++++++++++++++++++++- 1 file changed, 163 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index f831266b1b..8c5a2e68ef 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -12,6 +12,14 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Online.Chat; +using osu.Game.Overlays.Notifications; +using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Colour; +using osu.Game.Online.API; +using osu.Game.Configuration; +using osu.Framework.Bindables; +using osu.Game.Users; namespace osu.Game.Overlays.Chat { @@ -20,6 +28,22 @@ namespace osu.Game.Overlays.Chat public readonly Channel Channel; protected ChatLineContainer ChatLineFlow; private OsuScrollContainer scroll; + public ColourInfo HighlightColour { get; set; } + + [Resolved(CanBeNull = true)] + private NotificationOverlay notificationOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private ChatOverlay chatOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private ChannelManager channelManager { get; set; } + + private Bindable notifyOnMention; + private Bindable notifyOnChat; + private Bindable highlightWords; + private Bindable ignoreList; + private Bindable localUser; public DrawableChannel(Channel channel) { @@ -28,8 +52,15 @@ namespace osu.Game.Overlays.Chat } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours, OsuConfigManager config, IAPIProvider api) { + notifyOnMention = config.GetBindable(OsuSetting.ChatHighlightName); + notifyOnChat = config.GetBindable(OsuSetting.ChatMessageNotification); + highlightWords = config.GetBindable(OsuSetting.HighlightWords); + ignoreList = config.GetBindable(OsuSetting.IgnoreList); + localUser = api.LocalUser; + HighlightColour = colours.Blue; + Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -77,10 +108,14 @@ namespace osu.Game.Overlays.Chat private void newMessagesArrived(IEnumerable newMessages) { // Add up to last Channel.MAX_HISTORY messages - var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); + var ignoredWords = getWords(ignoreList.Value); + var displayMessages = newMessages.Where(m => hasCaseInsensitive(getWords(m.Content), ignoredWords) == null); + displayMessages = displayMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); ChatLineFlow.AddRange(displayMessages.Select(CreateChatLine)); + checkForMentions(displayMessages); + if (scroll.IsScrolledToEnd(10) || !ChatLineFlow.Children.Any() || newMessages.Any(m => m is LocalMessage)) scrollToEnd(); @@ -96,6 +131,63 @@ namespace osu.Game.Overlays.Chat } } + private void checkForMentions(IEnumerable messages) + { + // only send notifications when chat overlay is **closed** + if (chatOverlay?.IsPresent == true && channelManager?.CurrentChannel.Value == Channel) + return; + + foreach (var message in messages) + { + var words = getWords(message.Content); + var username = localUser.Value.Username; + + if (message.Sender.Username == username) + continue; + + if (notifyOnChat.Value && Channel.Type == ChannelType.PM) + { + var notification = new MentionNotification(Channel, message.Sender.Username, () => + { + channelManager.CurrentChannel.Value = Channel; + HighlightMessage(message); + }, true); + + notificationOverlay?.Post(notification); + continue; + } + + if (notifyOnMention.Value && anyCaseInsensitive(words, username)) + { + var notification = new MentionNotification(Channel, message.Sender.Username, () => + { + channelManager.CurrentChannel.Value = Channel; + HighlightMessage(message); + }, false); + + notificationOverlay?.Post(notification); + continue; + } + + if (!string.IsNullOrWhiteSpace(highlightWords.Value)) + { + var matchedWord = hasCaseInsensitive(words, getWords(highlightWords.Value)); + + if (matchedWord != null) + { + var notification = new MentionNotification(Channel, message.Sender.Username, matchedWord, () => + { + channelManager.CurrentChannel.Value = Channel; + HighlightMessage(message); + }); + + notificationOverlay?.Post(notification); + continue; + } + } + } + } + private void pendingMessageResolved(Message existing, Message updated) { var found = ChatLineFlow.Children.LastOrDefault(c => c.Message == existing); @@ -110,13 +202,31 @@ namespace osu.Game.Overlays.Chat } } - private void messageRemoved(Message removed) + public void HighlightMessage(Message message) { - ChatLineFlow.Children.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + var chatLine = findChatLine(message); + scroll.ScrollTo(chatLine); + chatLine.FlashColour(HighlightColour, 5000, Easing.InExpo); } + private void messageRemoved(Message removed) + { + findChatLine(removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + } + + private ChatLine findChatLine(Message message) => ChatLineFlow.Children.FirstOrDefault(c => c.Message == message); + private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); + private string[] getWords(string input) => input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + /// + /// Finds the first matching string/word in both and (case-insensitive) + /// + private string hasCaseInsensitive(IEnumerable x, IEnumerable y) => x.FirstOrDefault(x2 => anyCaseInsensitive(y, x2)); + + private bool anyCaseInsensitive(IEnumerable x, string y) => x.Any(x2 => x2.Equals(y, StringComparison.InvariantCultureIgnoreCase)); + protected class ChatLineContainer : FillFlowContainer { protected override int Compare(Drawable x, Drawable y) @@ -127,5 +237,54 @@ namespace osu.Game.Overlays.Chat return xC.Message.CompareTo(yC.Message); } } + + private class MentionNotification : SimpleNotification + { + public MentionNotification(Channel channel, string username, Action onClick, bool isPm) : this(channel, onClick) + { + if (isPm) + { + Icon = FontAwesome.Solid.Envelope; + Text = $"You received a private message from '{username}'. Click to read it!"; + } + else + { + Icon = FontAwesome.Solid.At; + Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; + } + } + + public MentionNotification(Channel channel, string highlighter, string word, Action onClick) : this(channel, onClick) + { + Icon = FontAwesome.Solid.Highlighter; + Text = $"'{word}' was mentioned in chat by '{highlighter}'. Click to find out why!"; + } + + private MentionNotification(Channel channel, Action onClick) + { + Channel = channel; + this.onClick = onClick; + } + + private readonly Action onClick; + + public Channel Channel { get; } + + public override bool IsImportant => false; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, NotificationOverlay notificationOverlay, ChatOverlay chatOverlay) + { + IconBackgound.Colour = colours.PurpleDark; + Activated = delegate + { + notificationOverlay.Hide(); + chatOverlay.Show(); + onClick?.Invoke(); + + return true; + }; + } + } } } From 28d1fb181fae36a2bc35deac327e37a8b8d2e2e6 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 16 Dec 2019 01:14:51 +0100 Subject: [PATCH 004/520] Add missing license header for InGameChatSettings.cs My unit tests fail at a solution filter, let's hope AppVeyor says yes. --- .../Overlays/Settings/Sections/Online/InGameChatSettings.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs index e9cb1477ad..4d8d06e557 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs @@ -1,4 +1,7 @@ -using osu.Framework.Allocation; +// 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.Game.Configuration; From 20670730b99604f6b26d7eb653839873a8632030 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 16 Dec 2019 01:57:07 +0100 Subject: [PATCH 005/520] Resolve code formatting --- osu.Game/Overlays/Chat/DrawableChannel.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 7eec3bf18d..66ba2d1076 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -218,7 +218,6 @@ namespace osu.Game.Overlays.Chat }); notificationOverlay?.Post(notification); - continue; } } } @@ -331,7 +330,8 @@ namespace osu.Game.Overlays.Chat private class MentionNotification : SimpleNotification { - public MentionNotification(Channel channel, string username, Action onClick, bool isPm) : this(channel, onClick) + public MentionNotification(Channel channel, string username, Action onClick, bool isPm) + : this(channel, onClick) { if (isPm) { @@ -345,7 +345,8 @@ namespace osu.Game.Overlays.Chat } } - public MentionNotification(Channel channel, string highlighter, string word, Action onClick) : this(channel, onClick) + public MentionNotification(Channel channel, string highlighter, string word, Action onClick) + : this(channel, onClick) { Icon = FontAwesome.Solid.Highlighter; Text = $"'{word}' was mentioned in chat by '{highlighter}'. Click to find out why!"; @@ -353,13 +354,13 @@ namespace osu.Game.Overlays.Chat private MentionNotification(Channel channel, Action onClick) { - Channel = channel; + this.channel = channel; this.onClick = onClick; } private readonly Action onClick; - public Channel Channel { get; } + private readonly Channel channel; public override bool IsImportant => false; From 8b14090c950df960194e5f1763fea694ba1c7695 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 16 Dec 2019 02:13:26 +0100 Subject: [PATCH 006/520] Remove unused field --- osu.Game/Overlays/Chat/DrawableChannel.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 66ba2d1076..0ca3129d6c 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -183,7 +183,7 @@ namespace osu.Game.Overlays.Chat if (notifyOnChat.Value && Channel.Type == ChannelType.PM) { - var notification = new MentionNotification(Channel, message.Sender.Username, () => + var notification = new MentionNotification(message.Sender.Username, () => { channelManager.CurrentChannel.Value = Channel; HighlightMessage(message); @@ -195,7 +195,7 @@ namespace osu.Game.Overlays.Chat if (notifyOnMention.Value && anyCaseInsensitive(words, username)) { - var notification = new MentionNotification(Channel, message.Sender.Username, () => + var notification = new MentionNotification(message.Sender.Username, () => { channelManager.CurrentChannel.Value = Channel; HighlightMessage(message); @@ -211,7 +211,7 @@ namespace osu.Game.Overlays.Chat if (matchedWord != null) { - var notification = new MentionNotification(Channel, message.Sender.Username, matchedWord, () => + var notification = new MentionNotification(message.Sender.Username, matchedWord, () => { channelManager.CurrentChannel.Value = Channel; HighlightMessage(message); @@ -330,8 +330,8 @@ namespace osu.Game.Overlays.Chat private class MentionNotification : SimpleNotification { - public MentionNotification(Channel channel, string username, Action onClick, bool isPm) - : this(channel, onClick) + public MentionNotification(string username, Action onClick, bool isPm) + : this(onClick) { if (isPm) { @@ -345,23 +345,20 @@ namespace osu.Game.Overlays.Chat } } - public MentionNotification(Channel channel, string highlighter, string word, Action onClick) - : this(channel, onClick) + public MentionNotification(string highlighter, string word, Action onClick) + : this(onClick) { Icon = FontAwesome.Solid.Highlighter; Text = $"'{word}' was mentioned in chat by '{highlighter}'. Click to find out why!"; } - private MentionNotification(Channel channel, Action onClick) + private MentionNotification(Action onClick) { - this.channel = channel; this.onClick = onClick; } private readonly Action onClick; - private readonly Channel channel; - public override bool IsImportant => false; [BackgroundDependencyLoader] From 81d994abeda12ff1e0ddf635c77363f82ae96b4f Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 16 Dec 2019 03:22:14 +0100 Subject: [PATCH 007/520] Change ChatMessageNotification's LabelText --- .../Settings/Sections/Online/AlertsAndPrivacySettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs index d84bf4eb3f..0898ce3b84 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections.Online }, new SettingsCheckbox { - LabelText = "Show chat message notifications", + LabelText = "Show private message notifications", Bindable = config.GetBindable(OsuSetting.ChatMessageNotification) }, }; From eb3f851ce27eb3496c8f9d8882f81fc0f246630a Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 16 Dec 2019 03:22:55 +0100 Subject: [PATCH 008/520] Split Notification class into three separate ones --- osu.Game/Overlays/Chat/DrawableChannel.cs | 84 ++++++++++++++++------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 0ca3129d6c..bbfdb2dece 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -169,7 +169,7 @@ namespace osu.Game.Overlays.Chat private void checkForMentions(IEnumerable messages) { - // only send notifications when chat overlay is **closed** + // only send notifications when the chat overlay is **closed** and the channel is not visible. if (chatOverlay?.IsPresent == true && channelManager?.CurrentChannel.Value == Channel) return; @@ -183,11 +183,11 @@ namespace osu.Game.Overlays.Chat if (notifyOnChat.Value && Channel.Type == ChannelType.PM) { - var notification = new MentionNotification(message.Sender.Username, () => + var notification = new PrivateMessageNotification(message.Sender.Username, () => { channelManager.CurrentChannel.Value = Channel; HighlightMessage(message); - }, true); + }); notificationOverlay?.Post(notification); continue; @@ -199,7 +199,7 @@ namespace osu.Game.Overlays.Chat { channelManager.CurrentChannel.Value = Channel; HighlightMessage(message); - }, false); + }); notificationOverlay?.Post(notification); continue; @@ -211,7 +211,7 @@ namespace osu.Game.Overlays.Chat if (matchedWord != null) { - var notification = new MentionNotification(message.Sender.Username, matchedWord, () => + var notification = new HighlightNotification(message.Sender.Username, matchedWord, () => { channelManager.CurrentChannel.Value = Channel; HighlightMessage(message); @@ -328,32 +328,68 @@ namespace osu.Game.Overlays.Chat } } - private class MentionNotification : SimpleNotification + private class HighlightNotification : SimpleNotification { - public MentionNotification(string username, Action onClick, bool isPm) - : this(onClick) - { - if (isPm) - { - Icon = FontAwesome.Solid.Envelope; - Text = $"You received a private message from '{username}'. Click to read it!"; - } - else - { - Icon = FontAwesome.Solid.At; - Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; - } - } - - public MentionNotification(string highlighter, string word, Action onClick) - : this(onClick) + public HighlightNotification(string highlighter, string word, Action onClick) { Icon = FontAwesome.Solid.Highlighter; Text = $"'{word}' was mentioned in chat by '{highlighter}'. Click to find out why!"; + this.onClick = onClick; } - private MentionNotification(Action onClick) + private readonly Action onClick; + + public override bool IsImportant => false; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, NotificationOverlay notificationOverlay, ChatOverlay chatOverlay) { + IconBackgound.Colour = colours.PurpleDark; + Activated = delegate + { + notificationOverlay.Hide(); + chatOverlay.Show(); + onClick?.Invoke(); + + return true; + }; + } + } + + private class PrivateMessageNotification : SimpleNotification + { + public PrivateMessageNotification(string username, Action onClick) + { + Icon = FontAwesome.Solid.Envelope; + Text = $"You received a private message from '{username}'. Click to read it!"; + this.onClick = onClick; + } + + private readonly Action onClick; + + public override bool IsImportant => false; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, NotificationOverlay notificationOverlay, ChatOverlay chatOverlay) + { + IconBackgound.Colour = colours.PurpleDark; + Activated = delegate + { + notificationOverlay.Hide(); + chatOverlay.Show(); + onClick?.Invoke(); + + return true; + }; + } + } + + private class MentionNotification : SimpleNotification + { + public MentionNotification(string username, Action onClick) + { + Icon = FontAwesome.Solid.At; + Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; this.onClick = onClick; } From 0225372e8347a4cbafd8aff855ac52e614289691 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 16 Dec 2019 03:24:07 +0100 Subject: [PATCH 009/520] Rename method to ScrollToAndHighlightMessage --- osu.Game/Overlays/Chat/DrawableChannel.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index bbfdb2dece..ef4ab25df7 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -186,7 +186,7 @@ namespace osu.Game.Overlays.Chat var notification = new PrivateMessageNotification(message.Sender.Username, () => { channelManager.CurrentChannel.Value = Channel; - HighlightMessage(message); + ScrollToAndHighlightMessage(message); }); notificationOverlay?.Post(notification); @@ -198,7 +198,7 @@ namespace osu.Game.Overlays.Chat var notification = new MentionNotification(message.Sender.Username, () => { channelManager.CurrentChannel.Value = Channel; - HighlightMessage(message); + ScrollToAndHighlightMessage(message); }); notificationOverlay?.Post(notification); @@ -214,7 +214,7 @@ namespace osu.Game.Overlays.Chat var notification = new HighlightNotification(message.Sender.Username, matchedWord, () => { channelManager.CurrentChannel.Value = Channel; - HighlightMessage(message); + ScrollToAndHighlightMessage(message); }); notificationOverlay?.Post(notification); @@ -237,7 +237,7 @@ namespace osu.Game.Overlays.Chat } } - public void HighlightMessage(Message message) + public void ScrollToAndHighlightMessage(Message message) { var chatLine = findChatLine(message); scroll.ScrollTo(chatLine); From 997b51b1f86679239034ff157e72f28cd6cbb118 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 16 Dec 2019 03:26:30 +0100 Subject: [PATCH 010/520] Make messageRemoved use helper method --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index ef4ab25df7..6813b3464d 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -246,7 +246,7 @@ namespace osu.Game.Overlays.Chat private void messageRemoved(Message removed) { - chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + findChatLine(removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); } private IEnumerable chatLines => ChatLineFlow.Children.OfType(); From 1a1253a4aa66d7bc917322f56d05a61fc627bcc2 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 16 Dec 2019 03:27:19 +0100 Subject: [PATCH 011/520] Add null check to ScrollToAndHighlightMessage --- osu.Game/Overlays/Chat/DrawableChannel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 6813b3464d..1ca65a1da7 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -239,6 +239,9 @@ namespace osu.Game.Overlays.Chat public void ScrollToAndHighlightMessage(Message message) { + if (message is null) + return; + var chatLine = findChatLine(message); scroll.ScrollTo(chatLine); chatLine.FlashColour(HighlightColour, 5000, Easing.InExpo); From bea34e3aab4b37822ee3792b53851a6aba0ffaa5 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Tue, 17 Dec 2019 06:55:48 +0100 Subject: [PATCH 012/520] Make it possible to retrieve notifications from NotificationOverlay --- osu.Game/Overlays/NotificationOverlay.cs | 3 +++ osu.Game/Overlays/Notifications/NotificationSection.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 41160d10ec..2ae17b143a 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -13,6 +13,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Threading; +using System.Collections.Generic; namespace osu.Game.Overlays { @@ -22,6 +23,8 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; + public IEnumerable Notifications => sections.Children.SelectMany(s => s.Notifications); + private FlowContainer sections; /// diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index 17a2d4cf9f..320c0d6cb1 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -21,6 +21,8 @@ namespace osu.Game.Overlays.Notifications private FlowContainer notifications; + public IEnumerable Notifications => notifications.Children; + public int DisplayedCount => notifications.Count(n => !n.WasClosed); public int UnreadCount => notifications.Count(n => !n.WasClosed && !n.Read); From 02dc70be022a3837f556412d5dbf1b8725a27bee Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Tue, 17 Dec 2019 06:56:05 +0100 Subject: [PATCH 013/520] Make it possible to retrieve loaded channel drawables in ChatOverlay --- osu.Game/Overlays/ChatOverlay.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 33bcc4c139..bceb47c484 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -438,6 +438,11 @@ namespace osu.Game.Overlays textbox.Text = string.Empty; } + /// + /// Returns the loaded drawable for a channel. Returns null if not found. + /// + public DrawableChannel GetChannelDrawable(Channel channel) => loadedChannels.Find(drawable => drawable.Channel == channel); + private class TabsArea : Container { // IsHovered is used From b6c31e7764fb339907bcdea63a90e0cbb0f09637 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Tue, 17 Dec 2019 06:59:27 +0100 Subject: [PATCH 014/520] Remove ignore list, move code to MessageNotifier and add it to DI This also adds countable private message notifications. --- osu.Game/Configuration/OsuConfigManager.cs | 2 - osu.Game/Online/Chat/ChannelManager.cs | 14 +- osu.Game/Online/Chat/MessageNotifier.cs | 231 ++++++++++++++++++ osu.Game/OsuGame.cs | 3 + osu.Game/Overlays/Chat/DrawableChannel.cs | 177 +------------- .../Sections/Online/InGameChatSettings.cs | 7 +- 6 files changed, 250 insertions(+), 184 deletions(-) create mode 100644 osu.Game/Online/Chat/MessageNotifier.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index cb4feb360c..93d9068a2e 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -53,7 +53,6 @@ namespace osu.Game.Configuration Set(OsuSetting.ChatMessageNotification, true); Set(OsuSetting.HighlightWords, string.Empty); - Set(OsuSetting.IgnoreList, string.Empty); // Audio Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); @@ -198,7 +197,6 @@ namespace osu.Game.Configuration ChatHighlightName, ChatMessageNotification, HighlightWords, - IgnoreList, UIHoldActivationDelay, HitLighting, MenuBackgroundSource diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 1d8c5609d9..937acf2128 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -50,6 +50,9 @@ namespace osu.Game.Online.Chat private IAPIProvider api; + [Resolved] + private MessageNotifier messageNotifier { get; set; } + public readonly BindableBool HighPollRate = new BindableBool(); public ChannelManager() @@ -247,7 +250,16 @@ namespace osu.Game.Online.Chat var channels = JoinedChannels.ToList(); foreach (var group in messages.GroupBy(m => m.ChannelId)) - channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray()); + { + var channel = channels.Find(c => c.Id == group.Key); + + if (channel == null) + continue; + + var groupArray = group.ToArray(); + channel.AddNewMessages(groupArray); + messageNotifier.HandleMessages(channel, groupArray); + } } private void initializeChannels() diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs new file mode 100644 index 0000000000..61ec7351c4 --- /dev/null +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -0,0 +1,231 @@ +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.Sprites; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Online.API; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; +using osu.Game.Users; + +namespace osu.Game.Online.Chat +{ + /// + /// Component that handles creating and posting notifications for incoming messages. + /// + public class MessageNotifier : Component + { + [Resolved(CanBeNull = true)] + private NotificationOverlay notificationOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private ChatOverlay chatOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private ChannelManager channelManager { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + private Bindable notifyOnMention; + private Bindable notifyOnChat; + private Bindable highlightWords; + private Bindable localUser; + + /// + /// Determines if the user is able to see incoming messages. + /// + public bool IsActive => chatOverlay?.IsPresent == true; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, OsuConfigManager config, IAPIProvider api) + { + notifyOnMention = config.GetBindable(OsuSetting.ChatHighlightName); + notifyOnChat = config.GetBindable(OsuSetting.ChatMessageNotification); + highlightWords = config.GetBindable(OsuSetting.HighlightWords); + localUser = api.LocalUser; + } + + public void HandleMessages(Channel channel, IEnumerable messages) + { + // don't show if visible or not visible + if (IsActive && channelManager.CurrentChannel.Value == channel) + return; + + var channelDrawable = chatOverlay.GetChannelDrawable(channel); + if (channelDrawable == null) + return; + + foreach (var message in messages) + { + var words = getWords(message.Content); + var localUsername = localUser.Value.Username; + + if (message.Sender.Username == localUsername) + continue; + + void onClick() + { + if (channelManager != null) + channelManager.CurrentChannel.Value = channel; + + channelDrawable.ScrollToAndHighlightMessage(message); + } + + if (notifyOnChat.Value && channel.Type == ChannelType.PM) + { + var username = message.Sender.Username; + var existingNotification = notificationOverlay.Notifications.OfType().FirstOrDefault(n => n.Username == username); + + if (existingNotification == null) + { + var notification = new PrivateMessageNotification(username, onClick); + notificationOverlay?.Post(notification); + } + else + { + existingNotification.MessageCount++; + } + + continue; + } + if (notifyOnMention.Value && anyCaseInsensitive(words, localUsername)) + { + var notification = new MentionNotification(message.Sender.Username, onClick); + notificationOverlay?.Post(notification); + + continue; + } + if (!string.IsNullOrWhiteSpace(highlightWords.Value)) + { + var matchedWord = hasCaseInsensitive(words, getWords(highlightWords.Value)); + + if (matchedWord != null) + { + var notification = new HighlightNotification(message.Sender.Username, matchedWord, onClick); + notificationOverlay?.Post(notification); + } + } + } + } + + private static string[] getWords(string input) => input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + /// + /// Finds the first matching string/word in both and (case-insensitive) + /// + private static string hasCaseInsensitive(IEnumerable x, IEnumerable y) => x.FirstOrDefault(x2 => anyCaseInsensitive(y, x2)); + + private static bool anyCaseInsensitive(IEnumerable x, string y) => x.Any(x2 => x2.Equals(y, StringComparison.InvariantCultureIgnoreCase)); + + private class HighlightNotification : SimpleNotification + { + public HighlightNotification(string highlighter, string word, Action onClick) + { + Icon = FontAwesome.Solid.Highlighter; + Text = $"'{word}' was mentioned in chat by '{highlighter}'. Click to find out why!"; + this.onClick = onClick; + } + + private readonly Action onClick; + + public override bool IsImportant => false; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, NotificationOverlay notificationOverlay, ChatOverlay chatOverlay) + { + IconBackgound.Colour = colours.PurpleDark; + Activated = delegate + { + notificationOverlay.Hide(); + chatOverlay.Show(); + onClick?.Invoke(); + + return true; + }; + } + } + + private class PrivateMessageNotification : SimpleNotification + { + public PrivateMessageNotification(string username, Action onClick) + { + Icon = FontAwesome.Solid.Envelope; + Username = username; + MessageCount = 1; + this.onClick = onClick; + } + + private int messageCount = 0; + + public int MessageCount + { + get => messageCount; + set + { + messageCount = value; + if (messageCount > 1) + { + Text = $"You received {messageCount} private messages from '{Username}'. Click to read it!"; + } + else + { + Text = $"You received a private message from '{Username}'. Click to read it!"; + } + } + } + + public string Username { get; set; } + + private readonly Action onClick; + + public override bool IsImportant => false; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, NotificationOverlay notificationOverlay, ChatOverlay chatOverlay) + { + IconBackgound.Colour = colours.PurpleDark; + Activated = delegate + { + notificationOverlay.Hide(); + chatOverlay.Show(); + onClick?.Invoke(); + + return true; + }; + } + } + + private class MentionNotification : SimpleNotification + { + public MentionNotification(string username, Action onClick) + { + Icon = FontAwesome.Solid.At; + Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; + this.onClick = onClick; + } + + private readonly Action onClick; + + public override bool IsImportant => false; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, NotificationOverlay notificationOverlay, ChatOverlay chatOverlay) + { + IconBackgound.Colour = colours.PurpleDark; + Activated = delegate + { + notificationOverlay.Hide(); + chatOverlay.Show(); + onClick?.Invoke(); + + return true; + }; + } + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c7c746bed3..d89109e9b9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -58,6 +58,8 @@ namespace osu.Game private ChannelManager channelManager; + private MessageNotifier messageNotifier; + private NotificationOverlay notifications; private DirectOverlay direct; @@ -589,6 +591,7 @@ namespace osu.Game loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add, true); loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add, true); loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true); + loadComponentSingleFile(messageNotifier = new MessageNotifier(), AddInternal, true); loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true); loadComponentSingleFile(Settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true); var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true); diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 1ca65a1da7..74aac2a7cf 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -33,21 +33,6 @@ namespace osu.Game.Overlays.Chat private OsuScrollContainer scroll; public ColourInfo HighlightColour { get; set; } - [Resolved(CanBeNull = true)] - private NotificationOverlay notificationOverlay { get; set; } - - [Resolved(CanBeNull = true)] - private ChatOverlay chatOverlay { get; set; } - - [Resolved(CanBeNull = true)] - private ChannelManager channelManager { get; set; } - - private Bindable notifyOnMention; - private Bindable notifyOnChat; - private Bindable highlightWords; - private Bindable ignoreList; - private Bindable localUser; - [Resolved] private OsuColour colours { get; set; } @@ -58,13 +43,8 @@ namespace osu.Game.Overlays.Chat } [BackgroundDependencyLoader] - private void load(OsuColour colours, OsuConfigManager config, IAPIProvider api) + private void load(OsuColour colours) { - notifyOnMention = config.GetBindable(OsuSetting.ChatHighlightName); - notifyOnChat = config.GetBindable(OsuSetting.ChatMessageNotification); - highlightWords = config.GetBindable(OsuSetting.HighlightWords); - ignoreList = config.GetBindable(OsuSetting.IgnoreList); - localUser = api.LocalUser; HighlightColour = colours.Blue; Child = new OsuContextMenuContainer @@ -122,14 +102,10 @@ namespace osu.Game.Overlays.Chat bool shouldScrollToEnd = scroll.IsScrolledToEnd(10) || !chatLines.Any() || newMessages.Any(m => m is LocalMessage); // Add up to last Channel.MAX_HISTORY messages - var ignoredWords = getWords(ignoreList.Value); - var displayMessages = newMessages.Where(m => hasCaseInsensitive(getWords(m.Content), ignoredWords) == null); - displayMessages = displayMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); + var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); Message lastMessage = chatLines.LastOrDefault()?.Message; - checkForMentions(displayMessages); - foreach (var message in displayMessages) { if (lastMessage == null || lastMessage.Timestamp.ToLocalTime().Date != message.Timestamp.ToLocalTime().Date) @@ -167,62 +143,6 @@ namespace osu.Game.Overlays.Chat scrollToEnd(); } - private void checkForMentions(IEnumerable messages) - { - // only send notifications when the chat overlay is **closed** and the channel is not visible. - if (chatOverlay?.IsPresent == true && channelManager?.CurrentChannel.Value == Channel) - return; - - foreach (var message in messages) - { - var words = getWords(message.Content); - var username = localUser.Value.Username; - - if (message.Sender.Username == username) - continue; - - if (notifyOnChat.Value && Channel.Type == ChannelType.PM) - { - var notification = new PrivateMessageNotification(message.Sender.Username, () => - { - channelManager.CurrentChannel.Value = Channel; - ScrollToAndHighlightMessage(message); - }); - - notificationOverlay?.Post(notification); - continue; - } - - if (notifyOnMention.Value && anyCaseInsensitive(words, username)) - { - var notification = new MentionNotification(message.Sender.Username, () => - { - channelManager.CurrentChannel.Value = Channel; - ScrollToAndHighlightMessage(message); - }); - - notificationOverlay?.Post(notification); - continue; - } - - if (!string.IsNullOrWhiteSpace(highlightWords.Value)) - { - var matchedWord = hasCaseInsensitive(words, getWords(highlightWords.Value)); - - if (matchedWord != null) - { - var notification = new HighlightNotification(message.Sender.Username, matchedWord, () => - { - channelManager.CurrentChannel.Value = Channel; - ScrollToAndHighlightMessage(message); - }); - - notificationOverlay?.Post(notification); - } - } - } - } - private void pendingMessageResolved(Message existing, Message updated) { var found = chatLines.LastOrDefault(c => c.Message == existing); @@ -256,15 +176,6 @@ namespace osu.Game.Overlays.Chat private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); - private string[] getWords(string input) => input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - - /// - /// Finds the first matching string/word in both and (case-insensitive) - /// - private string hasCaseInsensitive(IEnumerable x, IEnumerable y) => x.FirstOrDefault(x2 => anyCaseInsensitive(y, x2)); - - private bool anyCaseInsensitive(IEnumerable x, string y) => x.Any(x2 => x2.Equals(y, StringComparison.InvariantCultureIgnoreCase)); - private ChatLine findChatLine(Message message) => chatLines.FirstOrDefault(c => c.Message == message); public class DaySeparator : Container @@ -330,89 +241,5 @@ namespace osu.Game.Overlays.Chat }; } } - - private class HighlightNotification : SimpleNotification - { - public HighlightNotification(string highlighter, string word, Action onClick) - { - Icon = FontAwesome.Solid.Highlighter; - Text = $"'{word}' was mentioned in chat by '{highlighter}'. Click to find out why!"; - this.onClick = onClick; - } - - private readonly Action onClick; - - public override bool IsImportant => false; - - [BackgroundDependencyLoader] - private void load(OsuColour colours, NotificationOverlay notificationOverlay, ChatOverlay chatOverlay) - { - IconBackgound.Colour = colours.PurpleDark; - Activated = delegate - { - notificationOverlay.Hide(); - chatOverlay.Show(); - onClick?.Invoke(); - - return true; - }; - } - } - - private class PrivateMessageNotification : SimpleNotification - { - public PrivateMessageNotification(string username, Action onClick) - { - Icon = FontAwesome.Solid.Envelope; - Text = $"You received a private message from '{username}'. Click to read it!"; - this.onClick = onClick; - } - - private readonly Action onClick; - - public override bool IsImportant => false; - - [BackgroundDependencyLoader] - private void load(OsuColour colours, NotificationOverlay notificationOverlay, ChatOverlay chatOverlay) - { - IconBackgound.Colour = colours.PurpleDark; - Activated = delegate - { - notificationOverlay.Hide(); - chatOverlay.Show(); - onClick?.Invoke(); - - return true; - }; - } - } - - private class MentionNotification : SimpleNotification - { - public MentionNotification(string username, Action onClick) - { - Icon = FontAwesome.Solid.At; - Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; - this.onClick = onClick; - } - - private readonly Action onClick; - - public override bool IsImportant => false; - - [BackgroundDependencyLoader] - private void load(OsuColour colours, NotificationOverlay notificationOverlay, ChatOverlay chatOverlay) - { - IconBackgound.Colour = colours.PurpleDark; - Activated = delegate - { - notificationOverlay.Hide(); - chatOverlay.Show(); - onClick?.Invoke(); - - return true; - }; - } - } } } diff --git a/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs index 4d8d06e557..781aa10618 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs @@ -16,16 +16,11 @@ namespace osu.Game.Overlays.Settings.Sections.Online { Children = new Drawable[] { - new SettingsTextBox - { - LabelText = "Chat ignore list (space-separated list)", - Bindable = config.GetBindable(OsuSetting.IgnoreList) - }, new SettingsTextBox { LabelText = "Chat highlight words (space-separated list)", Bindable = config.GetBindable(OsuSetting.HighlightWords) - }, + } }; } } From 7bdfd2e23ce1083cc52db42d021e6a125bba97a5 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Tue, 17 Dec 2019 07:04:55 +0100 Subject: [PATCH 015/520] All copyright goes to peppy --- osu.Game/Online/Chat/MessageNotifier.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 61ec7351c4..9ee5e90be8 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -1,4 +1,7 @@ -using System; +// 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.Linq; using osu.Framework.Allocation; From 0d812bce9f90a8a77cd413386e9a6c4cdea90c44 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 26 Dec 2019 03:32:40 +0100 Subject: [PATCH 016/520] WIP changes for code review --- osu.Game/Online/Chat/Channel.cs | 27 ++++++---- osu.Game/Online/Chat/ChannelManager.cs | 5 -- osu.Game/Online/Chat/MessageNotifier.cs | 65 +++++++++++++++++------ osu.Game/Overlays/Chat/DrawableChannel.cs | 6 +-- osu.Game/Overlays/ChatOverlay.cs | 41 ++++++++++++-- 5 files changed, 105 insertions(+), 39 deletions(-) diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 451174a73c..3e2a247d7f 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -44,7 +44,7 @@ namespace osu.Game.Online.Chat /// /// An event that fires when new messages arrived. /// - public event Action> NewMessagesArrived; + public event Action, bool> NewMessagesArrived; /// /// An event that fires when a pending message gets resolved. @@ -58,6 +58,11 @@ namespace osu.Game.Online.Chat public bool ReadOnly => false; //todo not yet used. + /// + /// Determines if the channel's previous messages have been loaded. + /// + public bool Populated { get; set; } = false; + public override string ToString() => Name; [JsonProperty(@"name")] @@ -105,7 +110,7 @@ namespace osu.Game.Online.Chat pendingMessages.Add(message); Messages.Add(message); - NewMessagesArrived?.Invoke(new[] { message }); + NewMessagesArrived?.Invoke(new[] { message }, Populated); } public bool MessagesLoaded; @@ -118,17 +123,21 @@ namespace osu.Game.Online.Chat { messages = messages.Except(Messages).ToArray(); - if (messages.Length == 0) return; + if (messages.Length != 0) + { + Messages.AddRange(messages); - Messages.AddRange(messages); + var maxMessageId = messages.Max(m => m.Id); + if (maxMessageId > LastMessageId) + LastMessageId = maxMessageId; - var maxMessageId = messages.Max(m => m.Id); - if (maxMessageId > LastMessageId) - LastMessageId = maxMessageId; + purgeOldMessages(); - purgeOldMessages(); + NewMessagesArrived?.Invoke(messages, Populated); + } - NewMessagesArrived?.Invoke(messages); + if (!Populated) + Populated = true; } /// diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 937acf2128..1bee12d8c8 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -49,10 +49,6 @@ namespace osu.Game.Online.Chat public IBindableList AvailableChannels => availableChannels; private IAPIProvider api; - - [Resolved] - private MessageNotifier messageNotifier { get; set; } - public readonly BindableBool HighPollRate = new BindableBool(); public ChannelManager() @@ -258,7 +254,6 @@ namespace osu.Game.Online.Chat var groupArray = group.ToArray(); channel.AddNewMessages(groupArray); - messageNotifier.HandleMessages(channel, groupArray); } } diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 9ee5e90be8..de079ce636 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Online.API; @@ -31,9 +32,6 @@ namespace osu.Game.Online.Chat [Resolved(CanBeNull = true)] private ChannelManager channelManager { get; set; } - [Resolved] - private OsuColour colours { get; set; } - private Bindable notifyOnMention; private Bindable notifyOnChat; private Bindable highlightWords; @@ -45,12 +43,53 @@ namespace osu.Game.Online.Chat public bool IsActive => chatOverlay?.IsPresent == true; [BackgroundDependencyLoader] - private void load(OsuColour colours, OsuConfigManager config, IAPIProvider api) + private void load(OsuConfigManager config, IAPIProvider api) { notifyOnMention = config.GetBindable(OsuSetting.ChatHighlightName); notifyOnChat = config.GetBindable(OsuSetting.ChatMessageNotification); highlightWords = config.GetBindable(OsuSetting.HighlightWords); localUser = api.LocalUser; + + // Listen for new messages + + channelManager.JoinedChannels.ItemsAdded += (joinedChannels) => + { + foreach (var channel in joinedChannels) + channel.NewMessagesArrived += channel_NewMessagesArrived; + }; + + channelManager.JoinedChannels.ItemsRemoved += (leftChannels) => + { + foreach (var channel in leftChannels) + channel.NewMessagesArrived -= channel_NewMessagesArrived; + }; + } + + private void channel_NewMessagesArrived(IEnumerable messages, bool populated) + { + if (messages == null || !messages.Any()) + return; + + if (!populated) + return; + + HandleMessages(messages.First().ChannelId, messages); + } + + /// + /// Resolves the channel id + /// + public void HandleMessages(long channelId, IEnumerable messages) + { + var channel = channelManager.JoinedChannels.FirstOrDefault(c => c.Id == channelId); + + if (channel == null) + { + Logger.Log($"Couldn't resolve channel id {channelId}", LoggingTarget.Information); + return; + } + + HandleMessages(channel, messages); } public void HandleMessages(Channel channel, IEnumerable messages) @@ -59,10 +98,6 @@ namespace osu.Game.Online.Chat if (IsActive && channelManager.CurrentChannel.Value == channel) return; - var channelDrawable = chatOverlay.GetChannelDrawable(channel); - if (channelDrawable == null) - return; - foreach (var message in messages) { var words = getWords(message.Content); @@ -73,20 +108,17 @@ namespace osu.Game.Online.Chat void onClick() { - if (channelManager != null) - channelManager.CurrentChannel.Value = channel; - - channelDrawable.ScrollToAndHighlightMessage(message); + chatOverlay.ScrollToAndHighlightMessage(channel, message); + chatOverlay.Show(); } if (notifyOnChat.Value && channel.Type == ChannelType.PM) { - var username = message.Sender.Username; - var existingNotification = notificationOverlay.Notifications.OfType().FirstOrDefault(n => n.Username == username); + var existingNotification = notificationOverlay.Notifications.OfType().FirstOrDefault(n => n.Username == message.Sender.Username); if (existingNotification == null) { - var notification = new PrivateMessageNotification(username, onClick); + var notification = new PrivateMessageNotification(message.Sender.Username, onClick); notificationOverlay?.Post(notification); } else @@ -139,13 +171,12 @@ namespace osu.Game.Online.Chat public override bool IsImportant => false; [BackgroundDependencyLoader] - private void load(OsuColour colours, NotificationOverlay notificationOverlay, ChatOverlay chatOverlay) + private void load(OsuColour colours, NotificationOverlay notificationOverlay) { IconBackgound.Colour = colours.PurpleDark; Activated = delegate { notificationOverlay.Hide(); - chatOverlay.Show(); onClick?.Invoke(); return true; diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 74aac2a7cf..57ce7fed7c 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Chat }, }; - newMessagesArrived(Channel.Messages); + newMessagesArrived(Channel.Messages, Channel.Populated); Channel.NewMessagesArrived += newMessagesArrived; Channel.MessageRemoved += messageRemoved; @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Chat Colour = colours.ChatBlue.Lighten(0.7f), }; - private void newMessagesArrived(IEnumerable newMessages) + private void newMessagesArrived(IEnumerable newMessages, bool populated) { bool shouldScrollToEnd = scroll.IsScrolledToEnd(10) || !chatLines.Any() || newMessages.Any(m => m is LocalMessage); @@ -164,7 +164,7 @@ namespace osu.Game.Overlays.Chat var chatLine = findChatLine(message); scroll.ScrollTo(chatLine); - chatLine.FlashColour(HighlightColour, 5000, Easing.InExpo); + chatLine.FlashColour(HighlightColour, 7500, Easing.InExpo); } private void messageRemoved(Message removed) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index bceb47c484..ab74439f9a 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -22,6 +22,7 @@ using osu.Game.Overlays.Chat.Selection; using osu.Game.Overlays.Chat.Tabs; using osuTK.Input; using osu.Framework.Graphics.Sprites; +using System; namespace osu.Game.Overlays { @@ -60,6 +61,8 @@ namespace osu.Game.Overlays private Container channelSelectionContainer; protected ChannelSelectionOverlay ChannelSelectionOverlay; + private Message highlightingMessage { get; set; } + public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) || (ChannelSelectionOverlay.State.Value == Visibility.Visible && ChannelSelectionOverlay.ReceivePositionalInputAt(screenSpacePos)); @@ -252,15 +255,14 @@ namespace osu.Game.Overlays if (ChannelTabControl.Current.Value != e.NewValue) Scheduler.Add(() => ChannelTabControl.Current.Value = e.NewValue); - var loaded = loadedChannels.Find(d => d.Channel == e.NewValue); + var loaded = GetChannelDrawable(e.NewValue); if (loaded == null) { currentChannelContainer.FadeOut(500, Easing.OutQuint); loading.Show(); - loaded = new DrawableChannel(e.NewValue); - loadedChannels.Add(loaded); + loaded = loadChannelDrawable(e.NewValue); LoadComponentAsync(loaded, l => { if (currentChannel.Value != e.NewValue) @@ -271,6 +273,12 @@ namespace osu.Game.Overlays currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); currentChannelContainer.FadeIn(500, Easing.OutQuint); + + if (highlightingMessage != null && highlightingMessage.ChannelId == e.NewValue.Id) + { + loaded.ScrollToAndHighlightMessage(highlightingMessage); + highlightingMessage = null; + } }); } else @@ -439,9 +447,32 @@ namespace osu.Game.Overlays } /// - /// Returns the loaded drawable for a channel. Returns null if not found. + /// Returns the loaded drawable for a channel. Creates new instance if is true. Otherwise returns null if not found. /// - public DrawableChannel GetChannelDrawable(Channel channel) => loadedChannels.Find(drawable => drawable.Channel == channel); + public DrawableChannel GetChannelDrawable(Channel channel, bool createIfUnloaded = false) + { + var result = loadedChannels.Find(drawable => drawable.Channel == channel); + + if (createIfUnloaded && result == null) + { + result = loadChannelDrawable(channel); + } + + return result; + } + + private DrawableChannel loadChannelDrawable(Channel channel) + { + var loaded = new DrawableChannel(channel); + loadedChannels.Add(loaded); + return loaded; + } + + public void ScrollToAndHighlightMessage(Channel channel, Message message) + { + highlightingMessage = message; + channelManager.CurrentChannel.Value = channel; + } private class TabsArea : Container { From 1b53c0ff7479ec59711882fa21086be4d9cd5cfe Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 16 Jan 2020 23:15:30 +0100 Subject: [PATCH 017/520] Remove populated property, and other changes --- osu.Game/Online/Chat/Channel.cs | 14 +--- osu.Game/Online/Chat/MessageNotifier.cs | 90 ++++++++++++++++------- osu.Game/Overlays/Chat/DrawableChannel.cs | 19 +---- osu.Game/Overlays/ChatOverlay.cs | 43 +---------- 4 files changed, 71 insertions(+), 95 deletions(-) diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 3e2a247d7f..3257774a27 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -44,7 +44,7 @@ namespace osu.Game.Online.Chat /// /// An event that fires when new messages arrived. /// - public event Action, bool> NewMessagesArrived; + public event Action> NewMessagesArrived; /// /// An event that fires when a pending message gets resolved. @@ -58,11 +58,6 @@ namespace osu.Game.Online.Chat public bool ReadOnly => false; //todo not yet used. - /// - /// Determines if the channel's previous messages have been loaded. - /// - public bool Populated { get; set; } = false; - public override string ToString() => Name; [JsonProperty(@"name")] @@ -110,7 +105,7 @@ namespace osu.Game.Online.Chat pendingMessages.Add(message); Messages.Add(message); - NewMessagesArrived?.Invoke(new[] { message }, Populated); + NewMessagesArrived?.Invoke(new[] { message }); } public bool MessagesLoaded; @@ -133,11 +128,8 @@ namespace osu.Game.Online.Chat purgeOldMessages(); - NewMessagesArrived?.Invoke(messages, Populated); + NewMessagesArrived?.Invoke(messages); } - - if (!Populated) - Populated = true; } /// diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index de079ce636..8663cf4793 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -51,7 +51,6 @@ namespace osu.Game.Online.Chat localUser = api.LocalUser; // Listen for new messages - channelManager.JoinedChannels.ItemsAdded += (joinedChannels) => { foreach (var channel in joinedChannels) @@ -65,14 +64,11 @@ namespace osu.Game.Online.Chat }; } - private void channel_NewMessagesArrived(IEnumerable messages, bool populated) + private void channel_NewMessagesArrived(IEnumerable messages) { if (messages == null || !messages.Any()) return; - if (!populated) - return; - HandleMessages(messages.First().ChannelId, messages); } @@ -94,7 +90,7 @@ namespace osu.Game.Online.Chat public void HandleMessages(Channel channel, IEnumerable messages) { - // don't show if visible or not visible + // don't show if the ChatOverlay and the channel is visible. if (IsActive && channelManager.CurrentChannel.Value == channel) return; @@ -108,26 +104,36 @@ namespace osu.Game.Online.Chat void onClick() { - chatOverlay.ScrollToAndHighlightMessage(channel, message); + notificationOverlay.Hide(); chatOverlay.Show(); + channelManager.CurrentChannel.Value = channel; } + + if (notifyOnChat.Value && channel.Type == ChannelType.PM) { - var existingNotification = notificationOverlay.Notifications.OfType().FirstOrDefault(n => n.Username == message.Sender.Username); + // Scheduling because of possible "race-condition" (NotificationOverlay didn't add the notification yet). + Schedule(() => + { + var existingNotification = notificationOverlay.Notifications.OfType() + .FirstOrDefault(n => n.Username == message.Sender.Username); - if (existingNotification == null) - { - var notification = new PrivateMessageNotification(message.Sender.Username, onClick); - notificationOverlay?.Post(notification); - } - else - { - existingNotification.MessageCount++; - } + if (existingNotification == null) + { + var notification = new PrivateMessageNotification(message.Sender.Username, onClick); + notificationOverlay?.Post(notification); + } + else + { + existingNotification.MessageCount++; + } + }); + continue; } + if (notifyOnMention.Value && anyCaseInsensitive(words, localUsername)) { var notification = new MentionNotification(message.Sender.Username, onClick); @@ -135,6 +141,7 @@ namespace osu.Game.Online.Chat continue; } + if (!string.IsNullOrWhiteSpace(highlightWords.Value)) { var matchedWord = hasCaseInsensitive(words, getWords(highlightWords.Value)); @@ -146,6 +153,40 @@ namespace osu.Game.Online.Chat } } } + + //making sure if the notification drawer bugs out, we merge it afterwards again. + Schedule(() => mergeNotifications()); + } + + /// + /// Checks current notifications if they aren't merged, and merges them together again. + /// + private void mergeNotifications() + { + if (notificationOverlay == null) + { + return; + } + + var pmn = notificationOverlay.Notifications.OfType(); + + foreach (var notification in pmn) + { + var duplicates = pmn.Where(n => n.Username == notification.Username); + + if (duplicates.Count() < 2) + continue; + + var first = duplicates.First(); + foreach (var notification2 in duplicates) + { + if (notification2 == first) + continue; + + first.MessageCount += notification2.MessageCount; + notification2.Close(); + } + } } private static string[] getWords(string input) => input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); @@ -171,14 +212,12 @@ namespace osu.Game.Online.Chat public override bool IsImportant => false; [BackgroundDependencyLoader] - private void load(OsuColour colours, NotificationOverlay notificationOverlay) + private void load(OsuColour colours) { IconBackgound.Colour = colours.PurpleDark; Activated = delegate { - notificationOverlay.Hide(); onClick?.Invoke(); - return true; }; } @@ -202,6 +241,7 @@ namespace osu.Game.Online.Chat set { messageCount = value; + if (messageCount > 1) { Text = $"You received {messageCount} private messages from '{Username}'. Click to read it!"; @@ -220,15 +260,12 @@ namespace osu.Game.Online.Chat public override bool IsImportant => false; [BackgroundDependencyLoader] - private void load(OsuColour colours, NotificationOverlay notificationOverlay, ChatOverlay chatOverlay) + private void load(OsuColour colours) { IconBackgound.Colour = colours.PurpleDark; Activated = delegate { - notificationOverlay.Hide(); - chatOverlay.Show(); onClick?.Invoke(); - return true; }; } @@ -248,15 +285,12 @@ namespace osu.Game.Online.Chat public override bool IsImportant => false; [BackgroundDependencyLoader] - private void load(OsuColour colours, NotificationOverlay notificationOverlay, ChatOverlay chatOverlay) + private void load(OsuColour colours) { IconBackgound.Colour = colours.PurpleDark; Activated = delegate { - notificationOverlay.Hide(); - chatOverlay.Show(); onClick?.Invoke(); - return true; }; } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 57ce7fed7c..9c75e89249 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -8,17 +8,12 @@ using System.Linq; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Online.Chat; -using osu.Game.Overlays.Notifications; using osu.Game.Graphics; -using osu.Game.Online.API; -using osu.Game.Configuration; -using osu.Game.Users; using osu.Game.Graphics.Sprites; using osu.Framework.Graphics; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Sprites; @@ -67,7 +62,7 @@ namespace osu.Game.Overlays.Chat }, }; - newMessagesArrived(Channel.Messages, Channel.Populated); + newMessagesArrived(Channel.Messages); Channel.NewMessagesArrived += newMessagesArrived; Channel.MessageRemoved += messageRemoved; @@ -97,7 +92,7 @@ namespace osu.Game.Overlays.Chat Colour = colours.ChatBlue.Lighten(0.7f), }; - private void newMessagesArrived(IEnumerable newMessages, bool populated) + private void newMessagesArrived(IEnumerable newMessages) { bool shouldScrollToEnd = scroll.IsScrolledToEnd(10) || !chatLines.Any() || newMessages.Any(m => m is LocalMessage); @@ -157,16 +152,6 @@ namespace osu.Game.Overlays.Chat } } - public void ScrollToAndHighlightMessage(Message message) - { - if (message is null) - return; - - var chatLine = findChatLine(message); - scroll.ScrollTo(chatLine); - chatLine.FlashColour(HighlightColour, 7500, Easing.InExpo); - } - private void messageRemoved(Message removed) { findChatLine(removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index ab74439f9a..c2716cd585 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -22,7 +22,6 @@ using osu.Game.Overlays.Chat.Selection; using osu.Game.Overlays.Chat.Tabs; using osuTK.Input; using osu.Framework.Graphics.Sprites; -using System; namespace osu.Game.Overlays { @@ -61,8 +60,6 @@ namespace osu.Game.Overlays private Container channelSelectionContainer; protected ChannelSelectionOverlay ChannelSelectionOverlay; - private Message highlightingMessage { get; set; } - public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) || (ChannelSelectionOverlay.State.Value == Visibility.Visible && ChannelSelectionOverlay.ReceivePositionalInputAt(screenSpacePos)); @@ -255,14 +252,16 @@ namespace osu.Game.Overlays if (ChannelTabControl.Current.Value != e.NewValue) Scheduler.Add(() => ChannelTabControl.Current.Value = e.NewValue); - var loaded = GetChannelDrawable(e.NewValue); + var loaded = loadedChannels.Find(drawable => drawable.Channel == e.NewValue); if (loaded == null) { currentChannelContainer.FadeOut(500, Easing.OutQuint); loading.Show(); - loaded = loadChannelDrawable(e.NewValue); + loaded = new DrawableChannel(e.NewValue); + loadedChannels.Add(loaded); + LoadComponentAsync(loaded, l => { if (currentChannel.Value != e.NewValue) @@ -273,12 +272,6 @@ namespace osu.Game.Overlays currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); currentChannelContainer.FadeIn(500, Easing.OutQuint); - - if (highlightingMessage != null && highlightingMessage.ChannelId == e.NewValue.Id) - { - loaded.ScrollToAndHighlightMessage(highlightingMessage); - highlightingMessage = null; - } }); } else @@ -446,34 +439,6 @@ namespace osu.Game.Overlays textbox.Text = string.Empty; } - /// - /// Returns the loaded drawable for a channel. Creates new instance if is true. Otherwise returns null if not found. - /// - public DrawableChannel GetChannelDrawable(Channel channel, bool createIfUnloaded = false) - { - var result = loadedChannels.Find(drawable => drawable.Channel == channel); - - if (createIfUnloaded && result == null) - { - result = loadChannelDrawable(channel); - } - - return result; - } - - private DrawableChannel loadChannelDrawable(Channel channel) - { - var loaded = new DrawableChannel(channel); - loadedChannels.Add(loaded); - return loaded; - } - - public void ScrollToAndHighlightMessage(Channel channel, Message message) - { - highlightingMessage = message; - channelManager.CurrentChannel.Value = channel; - } - private class TabsArea : Container { // IsHovered is used From 5d244f48f7553a862b7f9435c7f62941eb7ec53b Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Fri, 17 Jan 2020 00:00:10 +0100 Subject: [PATCH 018/520] Use instance list instead of exposing NotifcationOverlay's notifications --- osu.Game/Online/Chat/MessageNotifier.cs | 31 ++++++++++++------- osu.Game/Overlays/NotificationOverlay.cs | 2 -- .../Notifications/NotificationSection.cs | 2 -- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 8663cf4793..c850fb4519 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -42,6 +42,8 @@ namespace osu.Game.Online.Chat /// public bool IsActive => chatOverlay?.IsPresent == true; + private List privateMessageNotifications = new List(); + [BackgroundDependencyLoader] private void load(OsuConfigManager config, IAPIProvider api) { @@ -96,12 +98,17 @@ namespace osu.Game.Online.Chat foreach (var message in messages) { - var words = getWords(message.Content); + // ignore messages that already have been read + if (message.Id < channel.LastReadId) + return; + var localUsername = localUser.Value.Username; if (message.Sender.Username == localUsername) continue; + var words = getWords(message.Content); + void onClick() { notificationOverlay.Hide(); @@ -109,20 +116,19 @@ namespace osu.Game.Online.Chat channelManager.CurrentChannel.Value = channel; } - - if (notifyOnChat.Value && channel.Type == ChannelType.PM) { // Scheduling because of possible "race-condition" (NotificationOverlay didn't add the notification yet). Schedule(() => { - var existingNotification = notificationOverlay.Notifications.OfType() - .FirstOrDefault(n => n.Username == message.Sender.Username); + var existingNotification = privateMessageNotifications.OfType() + .FirstOrDefault(n => n.Username == message.Sender.Username); if (existingNotification == null) { var notification = new PrivateMessageNotification(message.Sender.Username, onClick); notificationOverlay?.Post(notification); + privateMessageNotifications.Add(notification); } else { @@ -130,7 +136,6 @@ namespace osu.Game.Online.Chat } }); - continue; } @@ -196,9 +201,9 @@ namespace osu.Game.Online.Chat /// private static string hasCaseInsensitive(IEnumerable x, IEnumerable y) => x.FirstOrDefault(x2 => anyCaseInsensitive(y, x2)); - private static bool anyCaseInsensitive(IEnumerable x, string y) => x.Any(x2 => x2.Equals(y, StringComparison.InvariantCultureIgnoreCase)); + private static bool anyCaseInsensitive(IEnumerable x, string y) => x.Any(x2 => x2.Equals(y, StringComparison.OrdinalIgnoreCase)); - private class HighlightNotification : SimpleNotification + public class HighlightNotification : SimpleNotification { public HighlightNotification(string highlighter, string word, Action onClick) { @@ -223,7 +228,7 @@ namespace osu.Game.Online.Chat } } - private class PrivateMessageNotification : SimpleNotification + public class PrivateMessageNotification : SimpleNotification { public PrivateMessageNotification(string username, Action onClick) { @@ -260,18 +265,22 @@ namespace osu.Game.Online.Chat public override bool IsImportant => false; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, MessageNotifier notifier) { IconBackgound.Colour = colours.PurpleDark; Activated = delegate { onClick?.Invoke(); + + if (notifier.privateMessageNotifications.Contains(this)) + notifier.privateMessageNotifications.Remove(this); + return true; }; } } - private class MentionNotification : SimpleNotification + public class MentionNotification : SimpleNotification { public MentionNotification(string username, Action onClick) { diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 2ae17b143a..f36c13ab70 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -23,8 +23,6 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; - public IEnumerable Notifications => sections.Children.SelectMany(s => s.Notifications); - private FlowContainer sections; /// diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index 320c0d6cb1..17a2d4cf9f 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -21,8 +21,6 @@ namespace osu.Game.Overlays.Notifications private FlowContainer notifications; - public IEnumerable Notifications => notifications.Children; - public int DisplayedCount => notifications.Count(n => !n.WasClosed); public int UnreadCount => notifications.Count(n => !n.WasClosed && !n.Read); From f55cf03bd0f09c770e84c85f8d856c85a65bc255 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 18 Jan 2020 14:17:26 +0100 Subject: [PATCH 019/520] Remove unnecessary changes after rework --- osu.Game/Online/Chat/MessageNotifier.cs | 64 +++++-------------------- 1 file changed, 13 insertions(+), 51 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index c850fb4519..2715c42a95 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -118,24 +118,20 @@ namespace osu.Game.Online.Chat if (notifyOnChat.Value && channel.Type == ChannelType.PM) { - // Scheduling because of possible "race-condition" (NotificationOverlay didn't add the notification yet). - Schedule(() => - { - var existingNotification = privateMessageNotifications.OfType() - .FirstOrDefault(n => n.Username == message.Sender.Username); + var existingNotification = privateMessageNotifications.OfType() + .FirstOrDefault(n => n.Username == message.Sender.Username); + + if (existingNotification == null) + { + var notification = new PrivateMessageNotification(message.Sender.Username, onClick); + notificationOverlay?.Post(notification); + privateMessageNotifications.Add(notification); + } + else + { + existingNotification.MessageCount++; + } - if (existingNotification == null) - { - var notification = new PrivateMessageNotification(message.Sender.Username, onClick); - notificationOverlay?.Post(notification); - privateMessageNotifications.Add(notification); - } - else - { - existingNotification.MessageCount++; - } - }); - continue; } @@ -158,40 +154,6 @@ namespace osu.Game.Online.Chat } } } - - //making sure if the notification drawer bugs out, we merge it afterwards again. - Schedule(() => mergeNotifications()); - } - - /// - /// Checks current notifications if they aren't merged, and merges them together again. - /// - private void mergeNotifications() - { - if (notificationOverlay == null) - { - return; - } - - var pmn = notificationOverlay.Notifications.OfType(); - - foreach (var notification in pmn) - { - var duplicates = pmn.Where(n => n.Username == notification.Username); - - if (duplicates.Count() < 2) - continue; - - var first = duplicates.First(); - foreach (var notification2 in duplicates) - { - if (notification2 == first) - continue; - - first.MessageCount += notification2.MessageCount; - notification2.Close(); - } - } } private static string[] getWords(string input) => input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); From bc6f71fe97d150e2dc1911efececb14edce39de0 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 18 Jan 2020 15:27:55 +0100 Subject: [PATCH 020/520] Preserve current channel if ChatOverlay is being loaded in --- osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 7 ++++--- osu.Game/Overlays/ChatOverlay.cs | 8 +++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 4b1d595b44..e30c1678d5 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -59,15 +59,16 @@ namespace osu.Game.Overlays.Chat.Tabs /// /// Adds a channel to the ChannelTabControl. - /// The first channel added will automaticly selected. + /// The first channel added will automaticly selected if is true. /// /// The channel that is going to be added. - public void AddChannel(Channel channel) + /// If the current channel should be changed if none was selected before + public void AddChannel(Channel channel, bool setChannel = true) { if (!Items.Contains(channel)) AddItem(channel); - if (Current.Value == null) + if (Current.Value == null && setChannel) Current.Value = channel; } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 44772da3c1..4e69e4c9fc 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -221,8 +221,14 @@ namespace osu.Game.Overlays // TODO: consider scheduling bindable callbacks to not perform when overlay is not present. channelManager.JoinedChannels.ItemsAdded += onChannelAddedToJoinedChannels; channelManager.JoinedChannels.ItemsRemoved += onChannelRemovedFromJoinedChannels; + + bool channelSelected = channelManager.CurrentChannel.Value != null; + foreach (Channel channel in channelManager.JoinedChannels) - ChannelTabControl.AddChannel(channel); + ChannelTabControl.AddChannel(channel, !channelSelected); + + if (channelSelected) + ChannelTabControl.Current.Value = channelManager.CurrentChannel.Value; channelManager.AvailableChannels.ItemsAdded += availableChannelsChanged; channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged; From 8ddd36596e5cfd4b3933abbad5d5fc59179f6506 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 18 Jan 2020 15:40:55 +0100 Subject: [PATCH 021/520] Revert useless changes varying from properties, naming changes etc. --- osu.Game/Online/Chat/Channel.cs | 17 ++++++++--------- osu.Game/Online/Chat/ChannelManager.cs | 11 ++--------- osu.Game/Overlays/Chat/DrawableChannel.cs | 3 --- osu.Game/Overlays/ChatOverlay.cs | 3 +-- osu.Game/Overlays/NotificationOverlay.cs | 1 - 5 files changed, 11 insertions(+), 24 deletions(-) diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 1dea38f422..6f67a95f53 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -126,18 +126,17 @@ namespace osu.Game.Online.Chat { messages = messages.Except(Messages).ToArray(); - if (messages.Length != 0) - { - Messages.AddRange(messages); + if (messages.Length == 0) return; - var maxMessageId = messages.Max(m => m.Id); - if (maxMessageId > LastMessageId) - LastMessageId = maxMessageId; + Messages.AddRange(messages); - purgeOldMessages(); + var maxMessageId = messages.Max(m => m.Id); + if (maxMessageId > LastMessageId) + LastMessageId = maxMessageId; - NewMessagesArrived?.Invoke(messages); - } + purgeOldMessages(); + + NewMessagesArrived?.Invoke(messages); } /// diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 45c0df0677..4b5ec1cad0 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -49,6 +49,7 @@ namespace osu.Game.Online.Chat public IBindableList AvailableChannels => availableChannels; private IAPIProvider api; + public readonly BindableBool HighPollRate = new BindableBool(); public ChannelManager() @@ -246,15 +247,7 @@ namespace osu.Game.Online.Chat var channels = JoinedChannels.ToList(); foreach (var group in messages.GroupBy(m => m.ChannelId)) - { - var channel = channels.Find(c => c.Id == group.Key); - - if (channel == null) - continue; - - var groupArray = group.ToArray(); - channel.AddNewMessages(groupArray); - } + channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray()); } private void initializeChannels() diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 9c75e89249..a85b157175 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -26,7 +26,6 @@ namespace osu.Game.Overlays.Chat public readonly Channel Channel; protected FillFlowContainer ChatLineFlow; private OsuScrollContainer scroll; - public ColourInfo HighlightColour { get; set; } [Resolved] private OsuColour colours { get; set; } @@ -40,8 +39,6 @@ namespace osu.Game.Overlays.Chat [BackgroundDependencyLoader] private void load(OsuColour colours) { - HighlightColour = colours.Blue; - Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 4e69e4c9fc..9bd9f89665 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -259,7 +259,7 @@ namespace osu.Game.Overlays if (ChannelTabControl.Current.Value != e.NewValue) Scheduler.Add(() => ChannelTabControl.Current.Value = e.NewValue); - var loaded = loadedChannels.Find(drawable => drawable.Channel == e.NewValue); + var loaded = loadedChannels.Find(d => d.Channel == e.NewValue); if (loaded == null) { @@ -268,7 +268,6 @@ namespace osu.Game.Overlays loaded = new DrawableChannel(e.NewValue); loadedChannels.Add(loaded); - LoadComponentAsync(loaded, l => { if (currentChannel.Value != e.NewValue) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index f36c13ab70..41160d10ec 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -13,7 +13,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Threading; -using System.Collections.Generic; namespace osu.Game.Overlays { From 64fe9692ed2abd4411affa6fe39d5525085dbe8a Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 18 Jan 2020 15:57:51 +0100 Subject: [PATCH 022/520] Resolve CA errors --- osu.Game/Online/Chat/MessageNotifier.cs | 26 +++++++------------------ osu.Game/OsuGame.cs | 4 +--- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 2715c42a95..a2d6759863 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -53,13 +53,13 @@ namespace osu.Game.Online.Chat localUser = api.LocalUser; // Listen for new messages - channelManager.JoinedChannels.ItemsAdded += (joinedChannels) => + channelManager.JoinedChannels.ItemsAdded += joinedChannels => { foreach (var channel in joinedChannels) channel.NewMessagesArrived += channel_NewMessagesArrived; }; - channelManager.JoinedChannels.ItemsRemoved += (leftChannels) => + channelManager.JoinedChannels.ItemsRemoved += leftChannels => { foreach (var channel in leftChannels) channel.NewMessagesArrived -= channel_NewMessagesArrived; @@ -92,7 +92,7 @@ namespace osu.Game.Online.Chat public void HandleMessages(Channel channel, IEnumerable messages) { - // don't show if the ChatOverlay and the channel is visible. + // don't show if the ChatOverlay and the target channel is visible. if (IsActive && channelManager.CurrentChannel.Value == channel) return; @@ -118,8 +118,7 @@ namespace osu.Game.Online.Chat if (notifyOnChat.Value && channel.Type == ChannelType.PM) { - var existingNotification = privateMessageNotifications.OfType() - .FirstOrDefault(n => n.Username == message.Sender.Username); + var existingNotification = privateMessageNotifications.FirstOrDefault(n => n.Username == message.Sender.Username); if (existingNotification == null) { @@ -200,24 +199,13 @@ namespace osu.Game.Online.Chat this.onClick = onClick; } - private int messageCount = 0; + private int messageCount; public int MessageCount { get => messageCount; - set - { - messageCount = value; - - if (messageCount > 1) - { - Text = $"You received {messageCount} private messages from '{Username}'. Click to read it!"; - } - else - { - Text = $"You received a private message from '{Username}'. Click to read it!"; - } - } + set => Text = (messageCount = value) > 1 ? $"You received {messageCount} private messages from '{Username}'. Click to read it!" + : $"You received a private message from '{Username}'. Click to read it!"; } public string Username { get; set; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ff23375556..40b65b50e6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -59,8 +59,6 @@ namespace osu.Game private ChannelManager channelManager; - private MessageNotifier messageNotifier; - private NotificationOverlay notifications; private NowPlayingOverlay nowPlaying; @@ -615,7 +613,7 @@ namespace osu.Game loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add, true); loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add, true); loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true); - loadComponentSingleFile(messageNotifier = new MessageNotifier(), AddInternal, true); + loadComponentSingleFile(new MessageNotifier(), AddInternal, true); loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true); loadComponentSingleFile(Settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true); var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true); From 8a9c90c5e61b6b16b1f96c9f3fd225b58e923226 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 18 Jan 2020 16:18:17 +0100 Subject: [PATCH 023/520] Resolve CA errors #2 --- osu.Game/Online/Chat/MessageNotifier.cs | 5 ++--- osu.Game/Overlays/Chat/DrawableChannel.cs | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index a2d6759863..1637d2c2fe 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online.Chat /// public bool IsActive => chatOverlay?.IsPresent == true; - private List privateMessageNotifications = new List(); + private readonly List privateMessageNotifications = new List(); [BackgroundDependencyLoader] private void load(OsuConfigManager config, IAPIProvider api) @@ -204,8 +204,7 @@ namespace osu.Game.Online.Chat public int MessageCount { get => messageCount; - set => Text = (messageCount = value) > 1 ? $"You received {messageCount} private messages from '{Username}'. Click to read it!" - : $"You received a private message from '{Username}'. Click to read it!"; + set => Text = (messageCount = value) > 1 ? $"You received {messageCount} private messages from '{Username}'. Click to read it!" : $"You received a private message from '{Username}'. Click to read it!"; } public string Username { get; set; } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index a85b157175..b6c5a05c62 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -15,7 +15,6 @@ using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Shapes; From 32c20235171b7a873e7fbe7fe4f8c9fc4738da73 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 19 Jan 2020 17:20:54 +0100 Subject: [PATCH 024/520] Remove refactor in DrawableChannel --- osu.Game/Overlays/Chat/DrawableChannel.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index b6c5a05c62..d5f4d6c6d6 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -150,15 +150,13 @@ namespace osu.Game.Overlays.Chat private void messageRemoved(Message removed) { - findChatLine(removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); } private IEnumerable chatLines => ChatLineFlow.Children.OfType(); private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); - private ChatLine findChatLine(Message message) => chatLines.FirstOrDefault(c => c.Message == message); - public class DaySeparator : Container { public float TextSize From dd5478fe1ff3fe39bbc353e5eebff1aa00f6f460 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 19 Jan 2020 17:26:43 +0100 Subject: [PATCH 025/520] Remove highlighted/mentioned words --- osu.Game/Configuration/OsuConfigManager.cs | 2 -- osu.Game/Online/Chat/MessageNotifier.cs | 34 ------------------- .../Sections/Online/InGameChatSettings.cs | 27 --------------- .../Settings/Sections/OnlineSection.cs | 3 +- 4 files changed, 1 insertion(+), 65 deletions(-) delete mode 100644 osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 93d9068a2e..2968dadb40 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -52,8 +52,6 @@ namespace osu.Game.Configuration Set(OsuSetting.ChatHighlightName, true); Set(OsuSetting.ChatMessageNotification, true); - Set(OsuSetting.HighlightWords, string.Empty); - // Audio Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 1637d2c2fe..0e6da54e8d 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -141,17 +141,6 @@ namespace osu.Game.Online.Chat continue; } - - if (!string.IsNullOrWhiteSpace(highlightWords.Value)) - { - var matchedWord = hasCaseInsensitive(words, getWords(highlightWords.Value)); - - if (matchedWord != null) - { - var notification = new HighlightNotification(message.Sender.Username, matchedWord, onClick); - notificationOverlay?.Post(notification); - } - } } } @@ -164,30 +153,7 @@ namespace osu.Game.Online.Chat private static bool anyCaseInsensitive(IEnumerable x, string y) => x.Any(x2 => x2.Equals(y, StringComparison.OrdinalIgnoreCase)); - public class HighlightNotification : SimpleNotification - { - public HighlightNotification(string highlighter, string word, Action onClick) - { - Icon = FontAwesome.Solid.Highlighter; - Text = $"'{word}' was mentioned in chat by '{highlighter}'. Click to find out why!"; - this.onClick = onClick; - } - private readonly Action onClick; - - public override bool IsImportant => false; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IconBackgound.Colour = colours.PurpleDark; - Activated = delegate - { - onClick?.Invoke(); - return true; - }; - } - } public class PrivateMessageNotification : SimpleNotification { diff --git a/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs deleted file mode 100644 index 781aa10618..0000000000 --- a/osu.Game/Overlays/Settings/Sections/Online/InGameChatSettings.cs +++ /dev/null @@ -1,27 +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 osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Configuration; - -namespace osu.Game.Overlays.Settings.Sections.Online -{ - public class InGameChatSettings : SettingsSubsection - { - protected override string Header => "In-Game Chat"; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - Children = new Drawable[] - { - new SettingsTextBox - { - LabelText = "Chat highlight words (space-separated list)", - Bindable = config.GetBindable(OsuSetting.HighlightWords) - } - }; - } - } -} diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index 67a2e881d0..77aa81b429 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs @@ -17,8 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections Children = new Drawable[] { new WebSettings(), - new AlertsAndPrivacySettings(), - new InGameChatSettings() + new AlertsAndPrivacySettings() }; } } From 86ecaf223d4c92369330c525067b5328df08eb58 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 19 Jan 2020 17:36:38 +0100 Subject: [PATCH 026/520] Improve getWords() --- osu.Game/Online/Chat/MessageNotifier.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 0e6da54e8d..606882507f 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -144,7 +145,7 @@ namespace osu.Game.Online.Chat } } - private static string[] getWords(string input) => input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + private static IEnumerable getWords(string input) => Regex.Matches(input, @"\w+").Select(c => c.Value); /// /// Finds the first matching string/word in both and (case-insensitive) From 4feae82434a33472ea17856fc00827aa0fd9c96e Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 19 Jan 2020 17:55:17 +0100 Subject: [PATCH 027/520] Split HandleMessages method --- osu.Game/Online/Chat/MessageNotifier.cs | 93 ++++++++++++++----------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 606882507f..431cc7bb00 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -103,48 +103,52 @@ namespace osu.Game.Online.Chat if (message.Id < channel.LastReadId) return; + // ignore messages from yourself var localUsername = localUser.Value.Username; if (message.Sender.Username == localUsername) continue; - var words = getWords(message.Content); - - void onClick() - { - notificationOverlay.Hide(); - chatOverlay.Show(); - channelManager.CurrentChannel.Value = channel; - } - - if (notifyOnChat.Value && channel.Type == ChannelType.PM) - { - var existingNotification = privateMessageNotifications.FirstOrDefault(n => n.Username == message.Sender.Username); - - if (existingNotification == null) - { - var notification = new PrivateMessageNotification(message.Sender.Username, onClick); - notificationOverlay?.Post(notification); - privateMessageNotifications.Add(notification); - } - else - { - existingNotification.MessageCount++; - } - + if (checkForPMs(channel, message)) continue; - } - if (notifyOnMention.Value && anyCaseInsensitive(words, localUsername)) - { - var notification = new MentionNotification(message.Sender.Username, onClick); - notificationOverlay?.Post(notification); - - continue; - } + // change output to bool again if another "message processor" is added. + checkForMentions(channel, message, localUsername); } } + private bool checkForPMs(Channel channel, Message message) + { + if (!notifyOnChat.Value || channel.Type != ChannelType.PM) + return false; + + var existingNotification = privateMessageNotifications.FirstOrDefault(n => n.Username == message.Sender.Username); + + if (existingNotification == null) + { + var notification = new PrivateMessageNotification(message.Sender.Username, channel); + notificationOverlay?.Post(notification); + privateMessageNotifications.Add(notification); + } + else + { + existingNotification.MessageCount++; + } + + return true; + } + + private void checkForMentions(Channel channel, Message message, string username) + { + var words = getWords(message.Content); + + if (!notifyOnMention.Value || !anyCaseInsensitive(words, username)) + return; + + var notification = new MentionNotification(message.Sender.Username, channel); + notificationOverlay?.Post(notification); + } + private static IEnumerable getWords(string input) => Regex.Matches(input, @"\w+").Select(c => c.Value); /// @@ -158,12 +162,12 @@ namespace osu.Game.Online.Chat public class PrivateMessageNotification : SimpleNotification { - public PrivateMessageNotification(string username, Action onClick) + public PrivateMessageNotification(string username, Channel channel) { Icon = FontAwesome.Solid.Envelope; Username = username; MessageCount = 1; - this.onClick = onClick; + Channel = channel; } private int messageCount; @@ -176,17 +180,19 @@ namespace osu.Game.Online.Chat public string Username { get; set; } - private readonly Action onClick; + public Channel Channel { get; set; } public override bool IsImportant => false; [BackgroundDependencyLoader] - private void load(OsuColour colours, MessageNotifier notifier) + private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager, MessageNotifier notifier) { IconBackgound.Colour = colours.PurpleDark; Activated = delegate { - onClick?.Invoke(); + notificationOverlay.Hide(); + chatOverlay.Show(); + channelManager.CurrentChannel.Value = Channel; if (notifier.privateMessageNotifications.Contains(this)) notifier.privateMessageNotifications.Remove(this); @@ -198,24 +204,27 @@ namespace osu.Game.Online.Chat public class MentionNotification : SimpleNotification { - public MentionNotification(string username, Action onClick) + public MentionNotification(string username, Channel channel) { Icon = FontAwesome.Solid.At; Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; - this.onClick = onClick; + Channel = channel; } - private readonly Action onClick; + public Channel Channel { get; set; } public override bool IsImportant => false; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager) { IconBackgound.Colour = colours.PurpleDark; Activated = delegate { - onClick?.Invoke(); + notificationOverlay.Hide(); + chatOverlay.Show(); + channelManager.CurrentChannel.Value = Channel; + return true; }; } From 5f96940b7d8fb778991e9e9d3bfb3b445484cb96 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 19 Jan 2020 17:56:01 +0100 Subject: [PATCH 028/520] Remove unused injection --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index d5f4d6c6d6..4c196f758d 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Chat } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { Child = new OsuContextMenuContainer { From 1681e167383b54561b4fb4a86ecf35bf6b7a29e8 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 19 Jan 2020 18:20:42 +0100 Subject: [PATCH 029/520] Rework ChannelTabControl's AddChannel method to not auto select and let ChatOverlay handle this --- osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 7 +------ osu.Game/Overlays/ChatOverlay.cs | 7 ++----- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index e30c1678d5..4e6bc48b8a 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -59,17 +59,12 @@ namespace osu.Game.Overlays.Chat.Tabs /// /// Adds a channel to the ChannelTabControl. - /// The first channel added will automaticly selected if is true. /// /// The channel that is going to be added. - /// If the current channel should be changed if none was selected before - public void AddChannel(Channel channel, bool setChannel = true) + public void AddChannel(Channel channel) { if (!Items.Contains(channel)) AddItem(channel); - - if (Current.Value == null && setChannel) - Current.Value = channel; } /// diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 9bd9f89665..3eba0811e3 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -222,13 +222,10 @@ namespace osu.Game.Overlays channelManager.JoinedChannels.ItemsAdded += onChannelAddedToJoinedChannels; channelManager.JoinedChannels.ItemsRemoved += onChannelRemovedFromJoinedChannels; - bool channelSelected = channelManager.CurrentChannel.Value != null; - foreach (Channel channel in channelManager.JoinedChannels) - ChannelTabControl.AddChannel(channel, !channelSelected); + ChannelTabControl.AddChannel(channel); - if (channelSelected) - ChannelTabControl.Current.Value = channelManager.CurrentChannel.Value; + ChannelTabControl.Current.Value = channelManager.CurrentChannel.Value ?? channelManager.JoinedChannels.First(); channelManager.AvailableChannels.ItemsAdded += availableChannelsChanged; channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged; From 4b871f61e38de3a5b0c9dd507e9a5e0b30e496c8 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 19 Jan 2020 18:23:12 +0100 Subject: [PATCH 030/520] Use Humanizer for counting PMs in text --- osu.Game/Online/Chat/MessageNotifier.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 431cc7bb00..b7fc41e57f 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -175,7 +176,11 @@ namespace osu.Game.Online.Chat public int MessageCount { get => messageCount; - set => Text = (messageCount = value) > 1 ? $"You received {messageCount} private messages from '{Username}'. Click to read it!" : $"You received a private message from '{Username}'. Click to read it!"; + set + { + messageCount = value; + Text = $"You received {"private message".ToQuantity(messageCount)} from '{Username}'. Click to read it!"; + } } public string Username { get; set; } From 7d1fc388ce2eef2b55f1ebdfd8e22c23b64e0815 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 19 Jan 2020 18:34:48 +0100 Subject: [PATCH 031/520] Resolve code quality errors --- osu.Game/Online/Chat/MessageNotifier.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index b7fc41e57f..2e6d1befd2 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -36,7 +36,6 @@ namespace osu.Game.Online.Chat private Bindable notifyOnMention; private Bindable notifyOnChat; - private Bindable highlightWords; private Bindable localUser; /// @@ -159,8 +158,6 @@ namespace osu.Game.Online.Chat private static bool anyCaseInsensitive(IEnumerable x, string y) => x.Any(x2 => x2.Equals(y, StringComparison.OrdinalIgnoreCase)); - - public class PrivateMessageNotification : SimpleNotification { public PrivateMessageNotification(string username, Channel channel) From be2a88c8a503fe31539872014a77473635f653b8 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 19 Jan 2020 18:40:17 +0100 Subject: [PATCH 032/520] Remove left over config entry --- osu.Game/Configuration/OsuConfigManager.cs | 1 - osu.Game/Online/Chat/MessageNotifier.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 2968dadb40..42b757c326 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -194,7 +194,6 @@ namespace osu.Game.Configuration IntroSequence, ChatHighlightName, ChatMessageNotification, - HighlightWords, UIHoldActivationDelay, HitLighting, MenuBackgroundSource diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 2e6d1befd2..58a3dd51a9 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -50,7 +50,6 @@ namespace osu.Game.Online.Chat { notifyOnMention = config.GetBindable(OsuSetting.ChatHighlightName); notifyOnChat = config.GetBindable(OsuSetting.ChatMessageNotification); - highlightWords = config.GetBindable(OsuSetting.HighlightWords); localUser = api.LocalUser; // Listen for new messages From f98347b3bba3a9f5f84d388709aecff51c12645e Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 19 Jan 2020 18:56:15 +0100 Subject: [PATCH 033/520] Allow no channels to be present --- osu.Game/Overlays/ChatOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 3eba0811e3..f49f5ef18b 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -225,7 +225,7 @@ namespace osu.Game.Overlays foreach (Channel channel in channelManager.JoinedChannels) ChannelTabControl.AddChannel(channel); - ChannelTabControl.Current.Value = channelManager.CurrentChannel.Value ?? channelManager.JoinedChannels.First(); + ChannelTabControl.Current.Value = channelManager.CurrentChannel.Value ?? channelManager.JoinedChannels.FirstOrDefault(); channelManager.AvailableChannels.ItemsAdded += availableChannelsChanged; channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged; From 63c8ae8211b548dec9cc8bbf3b86504d81d0098f Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Tue, 21 Jan 2020 23:42:15 +0100 Subject: [PATCH 034/520] Use IDs for checking against message author --- osu.Game/Online/Chat/MessageNotifier.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 58a3dd51a9..5739054750 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -103,16 +103,14 @@ namespace osu.Game.Online.Chat return; // ignore messages from yourself - var localUsername = localUser.Value.Username; - - if (message.Sender.Username == localUsername) + if (message.Sender.Id == localUser.Value.Id) continue; if (checkForPMs(channel, message)) continue; // change output to bool again if another "message processor" is added. - checkForMentions(channel, message, localUsername); + checkForMentions(channel, message, localUser.Value.Username); } } From 4d6ff31134b481e6d537c3d94a6161701bde793c Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Tue, 21 Jan 2020 23:43:21 +0100 Subject: [PATCH 035/520] Wrap getWords() with anyCaseInsensitive() --- osu.Game/Online/Chat/MessageNotifier.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 5739054750..58941044c7 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -137,9 +137,7 @@ namespace osu.Game.Online.Chat private void checkForMentions(Channel channel, Message message, string username) { - var words = getWords(message.Content); - - if (!notifyOnMention.Value || !anyCaseInsensitive(words, username)) + if (!notifyOnMention.Value || !anyCaseInsensitive(getWords(message.Content), username)) return; var notification = new MentionNotification(message.Sender.Username, channel); From 47a92a13b0745ff9c3f927bd66edc69c33fd8b62 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 22 Jan 2020 00:13:07 +0100 Subject: [PATCH 036/520] Change code comments --- osu.Game/Online/Chat/MessageNotifier.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 58941044c7..22d5ef303f 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -92,7 +92,7 @@ namespace osu.Game.Online.Chat public void HandleMessages(Channel channel, IEnumerable messages) { - // don't show if the ChatOverlay and the target channel is visible. + // Only send notifications, if ChatOverlay and the target channel aren't visible. if (IsActive && channelManager.CurrentChannel.Value == channel) return; @@ -102,7 +102,6 @@ namespace osu.Game.Online.Chat if (message.Id < channel.LastReadId) return; - // ignore messages from yourself if (message.Sender.Id == localUser.Value.Id) continue; From 9fd494b057597b28c8e9708ec26b469891a37bea Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 22 Jan 2020 00:27:46 +0100 Subject: [PATCH 037/520] Fix order where messages are checked in --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 22d5ef303f..302600b687 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -96,7 +96,7 @@ namespace osu.Game.Online.Chat if (IsActive && channelManager.CurrentChannel.Value == channel) return; - foreach (var message in messages) + foreach (var message in messages.OrderByDescending(m => m.Id)) { // ignore messages that already have been read if (message.Id < channel.LastReadId) From 699547e1a214c226c96593acf2ef1eace1b67398 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 22 Jan 2020 00:28:08 +0100 Subject: [PATCH 038/520] Also exclude last read message --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 302600b687..a941b970fb 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -99,7 +99,7 @@ namespace osu.Game.Online.Chat foreach (var message in messages.OrderByDescending(m => m.Id)) { // ignore messages that already have been read - if (message.Id < channel.LastReadId) + if (message.Id <= channel.LastReadId) return; if (message.Sender.Id == localUser.Value.Id) From 5978e2c0e222e64736199df9a84f39c128fe8b52 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 22 Jan 2020 00:28:59 +0100 Subject: [PATCH 039/520] Redo how instances of PM notifications are removed --- osu.Game/Online/Chat/MessageNotifier.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index a941b970fb..9819754b85 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -122,7 +122,8 @@ namespace osu.Game.Online.Chat if (existingNotification == null) { - var notification = new PrivateMessageNotification(message.Sender.Username, channel); + var notification = new PrivateMessageNotification(message.Sender.Username, channel, (n) => privateMessageNotifications.Remove(n)); + notificationOverlay?.Post(notification); privateMessageNotifications.Add(notification); } @@ -154,12 +155,13 @@ namespace osu.Game.Online.Chat public class PrivateMessageNotification : SimpleNotification { - public PrivateMessageNotification(string username, Channel channel) + public PrivateMessageNotification(string username, Channel channel, Action onRemove) { Icon = FontAwesome.Solid.Envelope; Username = username; MessageCount = 1; Channel = channel; + OnRemove = onRemove; } private int messageCount; @@ -178,23 +180,28 @@ namespace osu.Game.Online.Chat public Channel Channel { get; set; } + public Action OnRemove { get; set; } + public override bool IsImportant => false; [BackgroundDependencyLoader] - private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager, MessageNotifier notifier) + private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager) { IconBackgound.Colour = colours.PurpleDark; + Activated = delegate { notificationOverlay.Hide(); chatOverlay.Show(); channelManager.CurrentChannel.Value = Channel; - if (notifier.privateMessageNotifications.Contains(this)) - notifier.privateMessageNotifications.Remove(this); - return true; }; + + Closed += delegate + { + OnRemove.Invoke(this); + }; } } @@ -215,6 +222,7 @@ namespace osu.Game.Online.Chat private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager) { IconBackgound.Colour = colours.PurpleDark; + Activated = delegate { notificationOverlay.Hide(); From 795051e25699fa9d4b3c9f6f1df73ef9db92358d Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 22 Jan 2020 00:29:12 +0100 Subject: [PATCH 040/520] Prevent channel duplicates --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 9819754b85..c2ea94a279 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -79,7 +79,7 @@ namespace osu.Game.Online.Chat /// public void HandleMessages(long channelId, IEnumerable messages) { - var channel = channelManager.JoinedChannels.FirstOrDefault(c => c.Id == channelId); + var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == channelId); if (channel == null) { From 88ea1138b6fbabba95abad898beda23e1faa96d9 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 22 Jan 2020 00:31:44 +0100 Subject: [PATCH 041/520] Compile regex --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index c2ea94a279..7dc19e1370 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -144,7 +144,7 @@ namespace osu.Game.Online.Chat notificationOverlay?.Post(notification); } - private static IEnumerable getWords(string input) => Regex.Matches(input, @"\w+").Select(c => c.Value); + private static IEnumerable getWords(string input) => Regex.Matches(input, @"\w+", RegexOptions.Compiled).Select(c => c.Value); /// /// Finds the first matching string/word in both and (case-insensitive) From d29694d788e988fe47e22120cd2ff42445d2e2fb Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 22 Jan 2020 00:41:46 +0100 Subject: [PATCH 042/520] Add additional comment to explain the code order --- osu.Game/Online/Chat/MessageNotifier.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 7dc19e1370..c832259338 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -105,6 +105,7 @@ namespace osu.Game.Online.Chat if (message.Sender.Id == localUser.Value.Id) continue; + // check for private messages first, if true, skip checking mentions to prevent duplicate notifications about the same message. if (checkForPMs(channel, message)) continue; From 73d4b6a6be2d5e1a396670bb863fc2114365c779 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 22 Jan 2020 00:53:49 +0100 Subject: [PATCH 043/520] Remove redundant lambda signature parentheses :/ --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index c832259338..21e92c98e4 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -123,7 +123,7 @@ namespace osu.Game.Online.Chat if (existingNotification == null) { - var notification = new PrivateMessageNotification(message.Sender.Username, channel, (n) => privateMessageNotifications.Remove(n)); + var notification = new PrivateMessageNotification(message.Sender.Username, channel, n => privateMessageNotifications.Remove(n)); notificationOverlay?.Post(notification); privateMessageNotifications.Add(notification); From e4accb3344fd275692105b4de6eb025a0393918b Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 22 Jan 2020 10:47:51 +0100 Subject: [PATCH 044/520] Remove IsActive property as it never really made sense to have it in the first place --- osu.Game/Online/Chat/MessageNotifier.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 21e92c98e4..8cdafdfd9c 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -38,11 +38,6 @@ namespace osu.Game.Online.Chat private Bindable notifyOnChat; private Bindable localUser; - /// - /// Determines if the user is able to see incoming messages. - /// - public bool IsActive => chatOverlay?.IsPresent == true; - private readonly List privateMessageNotifications = new List(); [BackgroundDependencyLoader] @@ -93,7 +88,7 @@ namespace osu.Game.Online.Chat public void HandleMessages(Channel channel, IEnumerable messages) { // Only send notifications, if ChatOverlay and the target channel aren't visible. - if (IsActive && channelManager.CurrentChannel.Value == channel) + if (chatOverlay?.IsPresent == true && channelManager.CurrentChannel.Value == channel) return; foreach (var message in messages.OrderByDescending(m => m.Id)) From 771155e88251c948370921a456adfc972159123f Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 22 Jan 2020 10:48:55 +0100 Subject: [PATCH 045/520] No notification "debouncing" --- osu.Game/Online/Chat/MessageNotifier.cs | 38 +++---------------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 8cdafdfd9c..745bf43c02 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -38,8 +38,6 @@ namespace osu.Game.Online.Chat private Bindable notifyOnChat; private Bindable localUser; - private readonly List privateMessageNotifications = new List(); - [BackgroundDependencyLoader] private void load(OsuConfigManager config, IAPIProvider api) { @@ -114,19 +112,9 @@ namespace osu.Game.Online.Chat if (!notifyOnChat.Value || channel.Type != ChannelType.PM) return false; - var existingNotification = privateMessageNotifications.FirstOrDefault(n => n.Username == message.Sender.Username); + var notification = new PrivateMessageNotification(message.Sender.Username, channel); - if (existingNotification == null) - { - var notification = new PrivateMessageNotification(message.Sender.Username, channel, n => privateMessageNotifications.Remove(n)); - - notificationOverlay?.Post(notification); - privateMessageNotifications.Add(notification); - } - else - { - existingNotification.MessageCount++; - } + notificationOverlay?.Post(notification); return true; } @@ -151,25 +139,12 @@ namespace osu.Game.Online.Chat public class PrivateMessageNotification : SimpleNotification { - public PrivateMessageNotification(string username, Channel channel, Action onRemove) + public PrivateMessageNotification(string username, Channel channel) { Icon = FontAwesome.Solid.Envelope; Username = username; - MessageCount = 1; Channel = channel; - OnRemove = onRemove; - } - - private int messageCount; - - public int MessageCount - { - get => messageCount; - set - { - messageCount = value; - Text = $"You received {"private message".ToQuantity(messageCount)} from '{Username}'. Click to read it!"; - } + Text = $"You received a private message from '{Username}'. Click to read it!"; } public string Username { get; set; } @@ -193,11 +168,6 @@ namespace osu.Game.Online.Chat return true; }; - - Closed += delegate - { - OnRemove.Invoke(this); - }; } } From 3d2625836ac0dd63b83401ccb90ad27ae03b33f1 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 22 Jan 2020 10:50:27 +0100 Subject: [PATCH 046/520] Remove static from getWords method --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 745bf43c02..71139c81b7 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -128,7 +128,7 @@ namespace osu.Game.Online.Chat notificationOverlay?.Post(notification); } - private static IEnumerable getWords(string input) => Regex.Matches(input, @"\w+", RegexOptions.Compiled).Select(c => c.Value); + private IEnumerable getWords(string input) => Regex.Matches(input, @"\w+", RegexOptions.Compiled).Select(c => c.Value); /// /// Finds the first matching string/word in both and (case-insensitive) From c6f450f93295f5ee65696ed179efd68706d2f0b6 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 22 Jan 2020 11:23:27 +0100 Subject: [PATCH 047/520] Resolve code analysis errors --- osu.Game/Online/Chat/MessageNotifier.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 71139c81b7..f6f1b9cb7d 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -147,9 +146,9 @@ namespace osu.Game.Online.Chat Text = $"You received a private message from '{Username}'. Click to read it!"; } - public string Username { get; set; } + public string Username { get; } - public Channel Channel { get; set; } + public Channel Channel { get; } public Action OnRemove { get; set; } @@ -180,7 +179,7 @@ namespace osu.Game.Online.Chat Channel = channel; } - public Channel Channel { get; set; } + public Channel Channel { get; } public override bool IsImportant => false; From 158b9690526846546755f5de6f572d0c540441e9 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 25 Jan 2020 14:40:16 +0100 Subject: [PATCH 048/520] Remove XML doc from HandleMessages --- osu.Game/Online/Chat/MessageNotifier.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index f6f1b9cb7d..a4fbb6fef3 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -66,9 +66,6 @@ namespace osu.Game.Online.Chat HandleMessages(messages.First().ChannelId, messages); } - /// - /// Resolves the channel id - /// public void HandleMessages(long channelId, IEnumerable messages) { var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == channelId); From 00da45ead4d9a4a36b1379e19a2f82863da6add2 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 25 Jan 2020 14:40:53 +0100 Subject: [PATCH 049/520] Matching strings instead of splitting --- osu.Game/Online/Chat/MessageNotifier.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index a4fbb6fef3..074b171022 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -117,21 +116,25 @@ namespace osu.Game.Online.Chat private void checkForMentions(Channel channel, Message message, string username) { - if (!notifyOnMention.Value || !anyCaseInsensitive(getWords(message.Content), username)) + if (!notifyOnMention.Value || !isMentioning(message.Content, username)) return; var notification = new MentionNotification(message.Sender.Username, channel); notificationOverlay?.Post(notification); } - private IEnumerable getWords(string input) => Regex.Matches(input, @"\w+", RegexOptions.Compiled).Select(c => c.Value); - /// - /// Finds the first matching string/word in both and (case-insensitive) + /// Checks if contains , if not, retries making spaces into underscores. /// - private static string hasCaseInsensitive(IEnumerable x, IEnumerable y) => x.FirstOrDefault(x2 => anyCaseInsensitive(y, x2)); + /// If the mentions the + private bool isMentioning(string message, string username) + { + // sanitize input to handle casing + message = message.ToLower(); + username = username.ToLower(); - private static bool anyCaseInsensitive(IEnumerable x, string y) => x.Any(x2 => x2.Equals(y, StringComparison.OrdinalIgnoreCase)); + return message.Contains(username) || message.Contains(username.Replace(' ', '_')); + } public class PrivateMessageNotification : SimpleNotification { From 16c500d0b0dd29191466608bcd8f7fa970e3419c Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 25 Jan 2020 16:41:45 +0100 Subject: [PATCH 050/520] Add mention tests --- osu.Game.Tests/Chat/MessageNotifierTests.cs | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 osu.Game.Tests/Chat/MessageNotifierTests.cs diff --git a/osu.Game.Tests/Chat/MessageNotifierTests.cs b/osu.Game.Tests/Chat/MessageNotifierTests.cs new file mode 100644 index 0000000000..d46e18b0b4 --- /dev/null +++ b/osu.Game.Tests/Chat/MessageNotifierTests.cs @@ -0,0 +1,27 @@ +using NUnit.Framework; +using osu.Game.Online.Chat; + +namespace osu.Game.Tests.Chat +{ + [TestFixture] + public class MessageNotifierTests + { + private readonly MessageNotifier messageNotifier = new MessageNotifier(); + + [Test] + public void TestMentions() + { + // Message (with mention, different casing) + Assert.IsTrue(messageNotifier.IsMentioning("Hey, Somebody Playing OSU!", "Somebody playing osu!")); + + // Message (with mention, underscores) + Assert.IsTrue(messageNotifier.IsMentioning("Hey, Somebody_playing_osu!", "Somebody playing osu!")); + + // Message (with mention, different casing, underscores) + Assert.IsTrue(messageNotifier.IsMentioning("Hey, Somebody_Playing_OSU!", "Somebody playing osu!")); + + // Message (without mention) + Assert.IsTrue(!messageNotifier.IsMentioning("peppy, can you please fix this?", "Cookiezi")); + } + } +} From e0ef6725494e75be59325183149d0ab8ed256f03 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 25 Jan 2020 16:43:51 +0100 Subject: [PATCH 051/520] Use binded list --- osu.Game/Online/Chat/MessageNotifier.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 074b171022..4d371f655d 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -35,6 +35,7 @@ namespace osu.Game.Online.Chat private Bindable notifyOnMention; private Bindable notifyOnChat; private Bindable localUser; + private BindableList joinedChannels = new BindableList(); [BackgroundDependencyLoader] private void load(OsuConfigManager config, IAPIProvider api) @@ -43,14 +44,16 @@ namespace osu.Game.Online.Chat notifyOnChat = config.GetBindable(OsuSetting.ChatMessageNotification); localUser = api.LocalUser; + channelManager.JoinedChannels.BindTo(joinedChannels); + // Listen for new messages - channelManager.JoinedChannels.ItemsAdded += joinedChannels => + joinedChannels.ItemsAdded += joinedChannels => { foreach (var channel in joinedChannels) channel.NewMessagesArrived += channel_NewMessagesArrived; }; - channelManager.JoinedChannels.ItemsRemoved += leftChannels => + joinedChannels.ItemsRemoved += leftChannels => { foreach (var channel in leftChannels) channel.NewMessagesArrived -= channel_NewMessagesArrived; @@ -116,7 +119,7 @@ namespace osu.Game.Online.Chat private void checkForMentions(Channel channel, Message message, string username) { - if (!notifyOnMention.Value || !isMentioning(message.Content, username)) + if (!notifyOnMention.Value || !IsMentioning(message.Content, username)) return; var notification = new MentionNotification(message.Sender.Username, channel); From f9def8355237dcc540cb4336c7132b7a4efa8a65 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 25 Jan 2020 16:44:45 +0100 Subject: [PATCH 052/520] Make IsMentioning public to allow it to be used for testing --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 4d371f655d..9f14a0fc21 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -130,7 +130,7 @@ namespace osu.Game.Online.Chat /// Checks if contains , if not, retries making spaces into underscores. /// /// If the mentions the - private bool isMentioning(string message, string username) + public bool IsMentioning(string message, string username) { // sanitize input to handle casing message = message.ToLower(); From 9e6fde7d09ebc8376df331f9b4e629bf34fe54f5 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 25 Jan 2020 16:50:12 +0100 Subject: [PATCH 053/520] Add license header --- osu.Game.Tests/Chat/MessageNotifierTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Chat/MessageNotifierTests.cs b/osu.Game.Tests/Chat/MessageNotifierTests.cs index d46e18b0b4..b5a9a63961 100644 --- a/osu.Game.Tests/Chat/MessageNotifierTests.cs +++ b/osu.Game.Tests/Chat/MessageNotifierTests.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; using osu.Game.Online.Chat; namespace osu.Game.Tests.Chat From 65644731e0d2e86280ed7b73a5c0bd9b06be1ff2 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 25 Jan 2020 17:03:39 +0100 Subject: [PATCH 054/520] Make field readonly --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 9f14a0fc21..41df7e7291 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -35,7 +35,7 @@ namespace osu.Game.Online.Chat private Bindable notifyOnMention; private Bindable notifyOnChat; private Bindable localUser; - private BindableList joinedChannels = new BindableList(); + private readonly BindableList joinedChannels = new BindableList(); [BackgroundDependencyLoader] private void load(OsuConfigManager config, IAPIProvider api) From 5e91a3f0f8ac53c66e3d77897e607956a50f4149 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 29 Jan 2020 01:59:52 +0100 Subject: [PATCH 055/520] Use IndexOf --- osu.Game/Online/Chat/MessageNotifier.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 41df7e7291..30784c9934 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -130,14 +130,7 @@ namespace osu.Game.Online.Chat /// Checks if contains , if not, retries making spaces into underscores. /// /// If the mentions the - public bool IsMentioning(string message, string username) - { - // sanitize input to handle casing - message = message.ToLower(); - username = username.ToLower(); - - return message.Contains(username) || message.Contains(username.Replace(' ', '_')); - } + public bool IsMentioning(string message, string username) => message.IndexOf(username, StringComparison.OrdinalIgnoreCase) != -1 || message.IndexOf(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase) != -1; public class PrivateMessageNotification : SimpleNotification { From d7a52aa80109cbb34b73f220ede35fa2e789a88b Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 29 Jan 2020 02:06:10 +0100 Subject: [PATCH 056/520] Use test scenes --- osu.Game.Tests/Chat/MessageNotifierTests.cs | 30 ------ .../Visual/Online/TestSceneMessageNotifier.cs | 99 +++++++++++++++++++ 2 files changed, 99 insertions(+), 30 deletions(-) delete mode 100644 osu.Game.Tests/Chat/MessageNotifierTests.cs create mode 100644 osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs diff --git a/osu.Game.Tests/Chat/MessageNotifierTests.cs b/osu.Game.Tests/Chat/MessageNotifierTests.cs deleted file mode 100644 index b5a9a63961..0000000000 --- a/osu.Game.Tests/Chat/MessageNotifierTests.cs +++ /dev/null @@ -1,30 +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 NUnit.Framework; -using osu.Game.Online.Chat; - -namespace osu.Game.Tests.Chat -{ - [TestFixture] - public class MessageNotifierTests - { - private readonly MessageNotifier messageNotifier = new MessageNotifier(); - - [Test] - public void TestMentions() - { - // Message (with mention, different casing) - Assert.IsTrue(messageNotifier.IsMentioning("Hey, Somebody Playing OSU!", "Somebody playing osu!")); - - // Message (with mention, underscores) - Assert.IsTrue(messageNotifier.IsMentioning("Hey, Somebody_playing_osu!", "Somebody playing osu!")); - - // Message (with mention, different casing, underscores) - Assert.IsTrue(messageNotifier.IsMentioning("Hey, Somebody_Playing_OSU!", "Somebody playing osu!")); - - // Message (without mention) - Assert.IsTrue(!messageNotifier.IsMentioning("peppy, can you please fix this?", "Cookiezi")); - } - } -} diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs new file mode 100644 index 0000000000..632f66354c --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -0,0 +1,99 @@ +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneMessageNotifier : OsuTestScene + { + private User friend; + private Channel publicChannel; + private Channel privateMesssageChannel; + private TestContainer testContainer; + + private int messageIdCounter; + + [SetUp] + public void Setup() + { + friend = new User() { Id = 0, Username = "Friend" }; + publicChannel = new Channel() { Id = 1, Name = "osu" }; + privateMesssageChannel = new Channel(friend) { Id = 2, Name = friend.Username, Type = ChannelType.PM }; + + Child = testContainer = new TestContainer(new Channel[] { publicChannel, privateMesssageChannel }) + { + RelativeSizeAxes = Axes.Both, + }; + + testContainer.ChatOverlay.Show(); + } + + [Test] + public void TestPublicChannelMention() + { + AddStep("Switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMesssageChannel); + + AddStep("Send regular message", () => publicChannel.AddNewMessages(new Message(messageIdCounter++) { + Content = "Hello everyone!", + Sender = friend, + ChannelId = publicChannel.Id + })); + AddAssert("Expect no notifications", () => testContainer.NotificationOverlay.UnreadCount.Value == 0); + + AddStep("Send message containing mention", () => publicChannel.AddNewMessages(new Message(messageIdCounter++) { + Content = $"Hello {API.LocalUser.Value.Username.ToLowerInvariant()}!", + Sender = friend, + ChannelId = publicChannel.Id + })); + AddAssert("Expect 1 notification", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); + } + + [Test] + public void TestPrivateMessageNotification() + { + AddStep("Send PM", () => privateMesssageChannel.AddNewMessages(new Message(messageIdCounter++) + { + Content = $"Hello {API.LocalUser.Value.Username}!", + Sender = friend, + ChannelId = privateMesssageChannel.Id + })); + AddAssert("Expect 1 notification", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); + } + + private class TestContainer : Container + { + private Channel[] channels; + + public TestContainer(Channel[] channels) => this.channels = channels; + + [Cached] + public ChannelManager ChannelManager { get; } = new ChannelManager(); + + [Cached] + public NotificationOverlay NotificationOverlay { get; } = new NotificationOverlay(); + + [Cached] + public MessageNotifier MessageNotifier { get; } = new MessageNotifier(); + + [Cached] + public ChatOverlay ChatOverlay { get; } = new ChatOverlay(); + + [BackgroundDependencyLoader] + private void load() + { + AddRange(new Drawable[] { ChannelManager, NotificationOverlay }); + + ((BindableList)ChannelManager.AvailableChannels).AddRange(channels); + + AddRange(new Drawable[] { ChatOverlay, MessageNotifier }); + + ((BindableList)ChannelManager.JoinedChannels).AddRange(channels); + } + } + } +} From 48231317d28880bd334b836da4d00ba7971e9494 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 29 Jan 2020 02:07:08 +0100 Subject: [PATCH 057/520] Make IsMentioning private --- osu.Game/Online/Chat/MessageNotifier.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 30784c9934..0e79a13917 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -119,7 +119,7 @@ namespace osu.Game.Online.Chat private void checkForMentions(Channel channel, Message message, string username) { - if (!notifyOnMention.Value || !IsMentioning(message.Content, username)) + if (!notifyOnMention.Value || !isMentioning(message.Content, username)) return; var notification = new MentionNotification(message.Sender.Username, channel); @@ -130,7 +130,7 @@ namespace osu.Game.Online.Chat /// Checks if contains , if not, retries making spaces into underscores. /// /// If the mentions the - public bool IsMentioning(string message, string username) => message.IndexOf(username, StringComparison.OrdinalIgnoreCase) != -1 || message.IndexOf(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase) != -1; + private bool isMentioning(string message, string username) => message.IndexOf(username, StringComparison.OrdinalIgnoreCase) != -1 || message.IndexOf(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase) != -1; public class PrivateMessageNotification : SimpleNotification { From e7881bd3811ed8eda28d8b4b51c1f4a93059dd94 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 29 Jan 2020 02:13:32 +0100 Subject: [PATCH 058/520] Single lines. --- .../Visual/Online/TestSceneMessageNotifier.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 632f66354c..51d5f837fc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -38,18 +38,10 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMesssageChannel); - AddStep("Send regular message", () => publicChannel.AddNewMessages(new Message(messageIdCounter++) { - Content = "Hello everyone!", - Sender = friend, - ChannelId = publicChannel.Id - })); + AddStep("Send regular message", () => publicChannel.AddNewMessages(new Message(messageIdCounter++) { Content = "Hello everyone!", Sender = friend, ChannelId = publicChannel.Id })); AddAssert("Expect no notifications", () => testContainer.NotificationOverlay.UnreadCount.Value == 0); - AddStep("Send message containing mention", () => publicChannel.AddNewMessages(new Message(messageIdCounter++) { - Content = $"Hello {API.LocalUser.Value.Username.ToLowerInvariant()}!", - Sender = friend, - ChannelId = publicChannel.Id - })); + AddStep("Send message containing mention", () => publicChannel.AddNewMessages(new Message(messageIdCounter++) { Content = $"Hello {API.LocalUser.Value.Username.ToLowerInvariant()}!", Sender = friend, ChannelId = publicChannel.Id })); AddAssert("Expect 1 notification", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); } From 4f109c02d31e90dc525f92ffa4291a1a15529592 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 29 Jan 2020 02:18:46 +0100 Subject: [PATCH 059/520] Add license header --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 51d5f837fc..df0611d33f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; From 738f1f0c565f56b6f36cfa2dddcfba46411566cd Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 29 Jan 2020 02:27:07 +0100 Subject: [PATCH 060/520] Turn lines into another single line --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index df0611d33f..64a3a75eda 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -51,12 +51,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestPrivateMessageNotification() { - AddStep("Send PM", () => privateMesssageChannel.AddNewMessages(new Message(messageIdCounter++) - { - Content = $"Hello {API.LocalUser.Value.Username}!", - Sender = friend, - ChannelId = privateMesssageChannel.Id - })); + AddStep("Send PM", () => privateMesssageChannel.AddNewMessages(new Message(messageIdCounter++) { Content = $"Hello {API.LocalUser.Value.Username}!", Sender = friend, ChannelId = privateMesssageChannel.Id })); AddAssert("Expect 1 notification", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); } From 25b080c78df4e842f759bbd9587daec28381116a Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 30 Jan 2020 03:41:21 +0100 Subject: [PATCH 061/520] Resolve CI/CA errors --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 64a3a75eda..1dcff4b017 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -24,11 +24,11 @@ namespace osu.Game.Tests.Visual.Online [SetUp] public void Setup() { - friend = new User() { Id = 0, Username = "Friend" }; - publicChannel = new Channel() { Id = 1, Name = "osu" }; + friend = new User { Id = 0, Username = "Friend" }; + publicChannel = new Channel { Id = 1, Name = "osu" }; privateMesssageChannel = new Channel(friend) { Id = 2, Name = friend.Username, Type = ChannelType.PM }; - Child = testContainer = new TestContainer(new Channel[] { publicChannel, privateMesssageChannel }) + Child = testContainer = new TestContainer(new[] { publicChannel, privateMesssageChannel }) { RelativeSizeAxes = Axes.Both, }; @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Online private class TestContainer : Container { - private Channel[] channels; + private readonly Channel[] channels; public TestContainer(Channel[] channels) => this.channels = channels; From 8523e3d205fcd8bff4ecf36566ac69ac11f3a040 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 30 Jan 2020 05:24:26 +0100 Subject: [PATCH 062/520] Schedule child updating --- .../Visual/Online/TestSceneMessageNotifier.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 1dcff4b017..4afa013345 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -28,12 +28,15 @@ namespace osu.Game.Tests.Visual.Online publicChannel = new Channel { Id = 1, Name = "osu" }; privateMesssageChannel = new Channel(friend) { Id = 2, Name = friend.Username, Type = ChannelType.PM }; - Child = testContainer = new TestContainer(new[] { publicChannel, privateMesssageChannel }) + Schedule(() => { - RelativeSizeAxes = Axes.Both, - }; + Child = testContainer = new TestContainer(new[] { publicChannel, privateMesssageChannel }) + { + RelativeSizeAxes = Axes.Both, + }; - testContainer.ChatOverlay.Show(); + testContainer.ChatOverlay.Show(); + }); } [Test] From 79f47fe7d791b57872158456a264cab4782ae35e Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 3 Feb 2020 22:08:01 +0100 Subject: [PATCH 063/520] Fix typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Bartłomiej Dach --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 4afa013345..33af4568ca 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Online { private User friend; private Channel publicChannel; - private Channel privateMesssageChannel; + private Channel privateMessageChannel; private TestContainer testContainer; private int messageIdCounter; From 176e1e7ec2b742bf079818646f3d5aaeffbdc932 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 3 Feb 2020 22:19:55 +0100 Subject: [PATCH 064/520] Rename references --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 33af4568ca..981aaf5b17 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -26,11 +26,11 @@ namespace osu.Game.Tests.Visual.Online { friend = new User { Id = 0, Username = "Friend" }; publicChannel = new Channel { Id = 1, Name = "osu" }; - privateMesssageChannel = new Channel(friend) { Id = 2, Name = friend.Username, Type = ChannelType.PM }; + privateMessageChannel = new Channel(friend) { Id = 2, Name = friend.Username, Type = ChannelType.PM }; Schedule(() => { - Child = testContainer = new TestContainer(new[] { publicChannel, privateMesssageChannel }) + Child = testContainer = new TestContainer(new[] { publicChannel, privateMessageChannel }) { RelativeSizeAxes = Axes.Both, }; @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestPublicChannelMention() { - AddStep("Switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMesssageChannel); + AddStep("Switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel); AddStep("Send regular message", () => publicChannel.AddNewMessages(new Message(messageIdCounter++) { Content = "Hello everyone!", Sender = friend, ChannelId = publicChannel.Id })); AddAssert("Expect no notifications", () => testContainer.NotificationOverlay.UnreadCount.Value == 0); @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestPrivateMessageNotification() { - AddStep("Send PM", () => privateMesssageChannel.AddNewMessages(new Message(messageIdCounter++) { Content = $"Hello {API.LocalUser.Value.Username}!", Sender = friend, ChannelId = privateMesssageChannel.Id })); + AddStep("Send PM", () => privateMessageChannel.AddNewMessages(new Message(messageIdCounter++) { Content = $"Hello {API.LocalUser.Value.Username}!", Sender = friend, ChannelId = privateMessageChannel.Id })); AddAssert("Expect 1 notification", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); } From 2936f83fd95eb62bae3f0737fd0c8445178226cc Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 3 Feb 2020 23:00:29 +0100 Subject: [PATCH 065/520] Improve load order --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 981aaf5b17..a935851282 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -79,12 +79,9 @@ namespace osu.Game.Tests.Visual.Online [BackgroundDependencyLoader] private void load() { - AddRange(new Drawable[] { ChannelManager, NotificationOverlay }); + AddRange(new Drawable[] { ChannelManager, ChatOverlay, NotificationOverlay, MessageNotifier }); ((BindableList)ChannelManager.AvailableChannels).AddRange(channels); - - AddRange(new Drawable[] { ChatOverlay, MessageNotifier }); - ((BindableList)ChannelManager.JoinedChannels).AddRange(channels); } } From 835d4f25ffbaefa3a79c04b778ef2cff5648eb82 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 3 Feb 2020 23:02:58 +0100 Subject: [PATCH 066/520] Add notification testing to tests and show notification overlay while testing --- .../Visual/Online/TestSceneMessageNotifier.cs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index a935851282..1490331266 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -1,18 +1,22 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Online.Chat; using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Users; +using osuTK.Input; namespace osu.Game.Tests.Visual.Online { - public class TestSceneMessageNotifier : OsuTestScene + public class TestSceneMessageNotifier : ManualInputManagerTestScene { private User friend; private Channel publicChannel; @@ -49,13 +53,35 @@ namespace osu.Game.Tests.Visual.Online AddStep("Send message containing mention", () => publicChannel.AddNewMessages(new Message(messageIdCounter++) { Content = $"Hello {API.LocalUser.Value.Username.ToLowerInvariant()}!", Sender = friend, ChannelId = publicChannel.Id })); AddAssert("Expect 1 notification", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); + + AddStep("Open notification overlay", () => testContainer.NotificationOverlay.Show()); + AddStep("Click notification", clickNotification); + + AddAssert("Expect ChatOverlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible); + AddAssert("Expect the public channel to be selected", () => testContainer.ChannelManager.CurrentChannel.Value == publicChannel); } [Test] public void TestPrivateMessageNotification() { + AddStep("Switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel); + AddStep("Send PM", () => privateMessageChannel.AddNewMessages(new Message(messageIdCounter++) { Content = $"Hello {API.LocalUser.Value.Username}!", Sender = friend, ChannelId = privateMessageChannel.Id })); AddAssert("Expect 1 notification", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); + + AddStep("Open notification overlay", () => testContainer.NotificationOverlay.Show()); + AddStep("Click notification", clickNotification); + + AddAssert("Expect ChatOverlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible); + AddAssert("Expect the PM channel to be selected", () => testContainer.ChannelManager.CurrentChannel.Value == privateMessageChannel); + } + + private void clickNotification() where T : Notification + { + var notification = testContainer.NotificationOverlay.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(notification); + InputManager.Click(MouseButton.Left); } private class TestContainer : Container From 4eedd82032b0be8599128d852a5d176881035fbc Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 3 Feb 2020 23:03:27 +0100 Subject: [PATCH 067/520] Don't unnecessarily expose properties --- osu.Game/Online/Chat/MessageNotifier.cs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 0e79a13917..4f04a78adc 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -137,16 +137,14 @@ namespace osu.Game.Online.Chat public PrivateMessageNotification(string username, Channel channel) { Icon = FontAwesome.Solid.Envelope; - Username = username; - Channel = channel; - Text = $"You received a private message from '{Username}'. Click to read it!"; + this.username = username; + this.channel = channel; + Text = $"You received a private message from '{this.username}'. Click to read it!"; } - public string Username { get; } + private readonly string username; - public Channel Channel { get; } - - public Action OnRemove { get; set; } + private readonly Channel channel; public override bool IsImportant => false; @@ -159,7 +157,7 @@ namespace osu.Game.Online.Chat { notificationOverlay.Hide(); chatOverlay.Show(); - channelManager.CurrentChannel.Value = Channel; + channelManager.CurrentChannel.Value = channel; return true; }; @@ -172,10 +170,10 @@ namespace osu.Game.Online.Chat { Icon = FontAwesome.Solid.At; Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; - Channel = channel; + this.channel = channel; } - public Channel Channel { get; } + private readonly Channel channel; public override bool IsImportant => false; @@ -188,7 +186,7 @@ namespace osu.Game.Online.Chat { notificationOverlay.Hide(); chatOverlay.Show(); - channelManager.CurrentChannel.Value = Channel; + channelManager.CurrentChannel.Value = channel; return true; }; From ea5eaba0a96cb3060e743952157ac4388854fa08 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 3 Feb 2020 23:05:46 +0100 Subject: [PATCH 068/520] Use ChannelManager.JoinChannel() --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 1490331266..ecd5892468 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -108,7 +108,9 @@ namespace osu.Game.Tests.Visual.Online AddRange(new Drawable[] { ChannelManager, ChatOverlay, NotificationOverlay, MessageNotifier }); ((BindableList)ChannelManager.AvailableChannels).AddRange(channels); - ((BindableList)ChannelManager.JoinedChannels).AddRange(channels); + + foreach (var channel in channels) + ChannelManager.JoinChannel(channel); } } } From f16b90a152a3fdb885dff743f31eee6f1f7178aa Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 3 Feb 2020 23:56:23 +0100 Subject: [PATCH 069/520] Remove username data from PrivateMessageNotification --- osu.Game/Online/Chat/MessageNotifier.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 4f04a78adc..0d821dff32 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -137,13 +137,10 @@ namespace osu.Game.Online.Chat public PrivateMessageNotification(string username, Channel channel) { Icon = FontAwesome.Solid.Envelope; - this.username = username; + Text = $"You received a private message from '{username}'. Click to read it!"; this.channel = channel; - Text = $"You received a private message from '{this.username}'. Click to read it!"; } - private readonly string username; - private readonly Channel channel; public override bool IsImportant => false; From a66fd17691182f862e80d5fc7507002edfbad2bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 Feb 2020 19:23:46 +0100 Subject: [PATCH 070/520] Expand test coverage --- .../Visual/Online/TestSceneMessageNotifier.cs | 96 +++++++++++++++---- 1 file changed, 80 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index ecd5892468..5e0a3994e1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -46,36 +46,100 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestPublicChannelMention() { - AddStep("Switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel); + AddStep("switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel); - AddStep("Send regular message", () => publicChannel.AddNewMessages(new Message(messageIdCounter++) { Content = "Hello everyone!", Sender = friend, ChannelId = publicChannel.Id })); - AddAssert("Expect no notifications", () => testContainer.NotificationOverlay.UnreadCount.Value == 0); + AddStep("receive public message", () => receiveMessage(friend, publicChannel, "Hello everyone")); + AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0); - AddStep("Send message containing mention", () => publicChannel.AddNewMessages(new Message(messageIdCounter++) { Content = $"Hello {API.LocalUser.Value.Username.ToLowerInvariant()}!", Sender = friend, ChannelId = publicChannel.Id })); - AddAssert("Expect 1 notification", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); + AddStep("receive message containing mention", () => receiveMessage(friend, publicChannel, $"Hello {API.LocalUser.Value.Username.ToLowerInvariant()}!")); + AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); - AddStep("Open notification overlay", () => testContainer.NotificationOverlay.Show()); - AddStep("Click notification", clickNotification); + AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show()); + AddStep("click notification", clickNotification); - AddAssert("Expect ChatOverlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible); - AddAssert("Expect the public channel to be selected", () => testContainer.ChannelManager.CurrentChannel.Value == publicChannel); + AddAssert("chat overlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible); + AddAssert("public channel is selected", () => testContainer.ChannelManager.CurrentChannel.Value == publicChannel); } [Test] public void TestPrivateMessageNotification() { - AddStep("Switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel); + AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel); - AddStep("Send PM", () => privateMessageChannel.AddNewMessages(new Message(messageIdCounter++) { Content = $"Hello {API.LocalUser.Value.Username}!", Sender = friend, ChannelId = privateMessageChannel.Id })); - AddAssert("Expect 1 notification", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); + AddStep("receive PM", () => receiveMessage(friend, privateMessageChannel, $"Hello {API.LocalUser.Value.Username}")); + AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); - AddStep("Open notification overlay", () => testContainer.NotificationOverlay.Show()); - AddStep("Click notification", clickNotification); + AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show()); + AddStep("click notification", clickNotification); - AddAssert("Expect ChatOverlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible); - AddAssert("Expect the PM channel to be selected", () => testContainer.ChannelManager.CurrentChannel.Value == privateMessageChannel); + AddAssert("chat overlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible); + AddAssert("PM channel is selected", () => testContainer.ChannelManager.CurrentChannel.Value == privateMessageChannel); } + [Test] + public void TestNoNotificationWhenPMChannelOpen() + { + AddStep("switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel); + + AddStep("receive PM", () => receiveMessage(friend, privateMessageChannel, "you're reading this, right?")); + + AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0); + } + + [Test] + public void TestNoNotificationWhenMentionedInOpenPublicChannel() + { + AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel); + + AddStep("receive mention", () => receiveMessage(friend, publicChannel, $"{API.LocalUser.Value.Username.ToUpperInvariant()} has been reading this")); + + AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0); + } + + [Test] + public void TestNoNotificationOnSelfMention() + { + AddStep("switch to PM channel", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel); + + AddStep("receive self-mention", () => receiveMessage(API.LocalUser.Value, publicChannel, $"my name is {API.LocalUser.Value.Username}")); + + AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0); + } + + [Test] + public void TestNoNotificationOnPMFromSelf() + { + AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel); + + AddStep("receive PM from self", () => receiveMessage(API.LocalUser.Value, privateMessageChannel, "hey hey")); + + AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0); + } + + [Test] + public void TestNotificationsNotFiredTwice() + { + AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel); + + AddStep("receive same PM twice", () => + { + var message = createMessage(friend, privateMessageChannel, "hey hey"); + privateMessageChannel.AddNewMessages(message, message); + }); + + AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show()); + AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); + } + + private void receiveMessage(User sender, Channel channel, string content) => channel.AddNewMessages(createMessage(sender, channel, content)); + + private Message createMessage(User sender, Channel channel, string content) => new Message(messageIdCounter++) + { + Content = content, + Sender = sender, + ChannelId = channel.Id + }; + private void clickNotification() where T : Notification { var notification = testContainer.NotificationOverlay.ChildrenOfType().Single(); From 9378b216e6879414566f63fc2b7c23f17337232b Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 5 Feb 2020 19:01:51 +0100 Subject: [PATCH 071/520] Lowercase the N inside channel_NewMessagesArrived --- osu.Game/Online/Chat/MessageNotifier.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 0d821dff32..166a073512 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -50,17 +50,17 @@ namespace osu.Game.Online.Chat joinedChannels.ItemsAdded += joinedChannels => { foreach (var channel in joinedChannels) - channel.NewMessagesArrived += channel_NewMessagesArrived; + channel.NewMessagesArrived += channel_newMessagesArrived; }; joinedChannels.ItemsRemoved += leftChannels => { foreach (var channel in leftChannels) - channel.NewMessagesArrived -= channel_NewMessagesArrived; + channel.NewMessagesArrived -= channel_newMessagesArrived; }; } - private void channel_NewMessagesArrived(IEnumerable messages) + private void channel_newMessagesArrived(IEnumerable messages) { if (messages == null || !messages.Any()) return; From 5875f2158c34ff169a2c85573a5f06d3db1f4d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 5 Feb 2020 19:20:16 +0100 Subject: [PATCH 072/520] Properly rename event handler --- osu.Game/Online/Chat/MessageNotifier.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 166a073512..016c25d8ab 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -50,17 +50,17 @@ namespace osu.Game.Online.Chat joinedChannels.ItemsAdded += joinedChannels => { foreach (var channel in joinedChannels) - channel.NewMessagesArrived += channel_newMessagesArrived; + channel.NewMessagesArrived += newMessagesArrived; }; joinedChannels.ItemsRemoved += leftChannels => { foreach (var channel in leftChannels) - channel.NewMessagesArrived -= channel_newMessagesArrived; + channel.NewMessagesArrived -= newMessagesArrived; }; } - private void channel_newMessagesArrived(IEnumerable messages) + private void newMessagesArrived(IEnumerable messages) { if (messages == null || !messages.Any()) return; From 7cd228db07f50d1f489bcf8e3514f48061dc168e Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Fri, 7 Feb 2020 16:50:22 +0100 Subject: [PATCH 073/520] Change notifyOnChat to notifyOnPM --- osu.Game/Online/Chat/MessageNotifier.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 166a073512..3b000675ac 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -33,7 +33,7 @@ namespace osu.Game.Online.Chat private ChannelManager channelManager { get; set; } private Bindable notifyOnMention; - private Bindable notifyOnChat; + private Bindable notifyOnPM; private Bindable localUser; private readonly BindableList joinedChannels = new BindableList(); @@ -41,7 +41,7 @@ namespace osu.Game.Online.Chat private void load(OsuConfigManager config, IAPIProvider api) { notifyOnMention = config.GetBindable(OsuSetting.ChatHighlightName); - notifyOnChat = config.GetBindable(OsuSetting.ChatMessageNotification); + notifyOnPM = config.GetBindable(OsuSetting.ChatMessageNotification); localUser = api.LocalUser; channelManager.JoinedChannels.BindTo(joinedChannels); @@ -107,7 +107,7 @@ namespace osu.Game.Online.Chat private bool checkForPMs(Channel channel, Message message) { - if (!notifyOnChat.Value || channel.Type != ChannelType.PM) + if (!notifyOnPM.Value || channel.Type != ChannelType.PM) return false; var notification = new PrivateMessageNotification(message.Sender.Username, channel); From dd86443264fb3861ee90a453f67eb013afcacd61 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Fri, 7 Feb 2020 16:51:37 +0100 Subject: [PATCH 074/520] Make isMentioning static --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 3b000675ac..975df9714e 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -130,7 +130,7 @@ namespace osu.Game.Online.Chat /// Checks if contains , if not, retries making spaces into underscores. /// /// If the mentions the - private bool isMentioning(string message, string username) => message.IndexOf(username, StringComparison.OrdinalIgnoreCase) != -1 || message.IndexOf(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase) != -1; + private static bool isMentioning(string message, string username) => message.IndexOf(username, StringComparison.OrdinalIgnoreCase) != -1 || message.IndexOf(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase) != -1; public class PrivateMessageNotification : SimpleNotification { From 41915df1f37fdd5bc9015a35b939f344d5ce0f42 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Fri, 7 Feb 2020 16:52:53 +0100 Subject: [PATCH 075/520] Change comment --- osu.Game/Online/Chat/MessageNotifier.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 975df9714e..8c92892a1e 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -96,7 +96,8 @@ namespace osu.Game.Online.Chat if (message.Sender.Id == localUser.Value.Id) continue; - // check for private messages first, if true, skip checking mentions to prevent duplicate notifications about the same message. + // check for private messages first, + // to avoid both posting two notifications about the same message if (checkForPMs(channel, message)) continue; From b6aedb22d8a5b169b23f528bd3430dfc82162074 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Sun, 15 Mar 2020 01:25:02 +0100 Subject: [PATCH 076/520] Add approachcircle mod --- .../Mods/OsuModApproachCircle.cs | 60 +++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + 2 files changed, 61 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs new file mode 100644 index 0000000000..42851e5cda --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs @@ -0,0 +1,60 @@ +// 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 osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Mods +{ + internal class OsuModApproachCircle : Mod, IApplicableToDrawableHitObjects + { + public override string Name => "Approach Circle"; + public override string Acronym => "AC"; + public override string Description => "Never trust the approach circles..."; + public override double ScoreMultiplier => 1; + + public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle; + + [SettingSource("Easing", "Change the easing type of the approach circles.", 0)] + public Bindable BindableEasing { get; } = new Bindable(); + + [SettingSource("Scale", "Change the factor of the approach circle scale.", 1)] + public BindableFloat Scale { get; } = new BindableFloat + { + Precision = 0.1f, + MinValue = 2, + MaxValue = 10, + Default = 4, + Value = 4, + }; + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + drawables.ForEach(drawable => + { + drawable.ApplyCustomUpdateState += (drawableHitObj, state) => + { + if (!(drawableHitObj is DrawableOsuHitObject drawableOsuHitObj) || !(drawableHitObj is DrawableHitCircle hitCircle)) return; + + var obj = drawableOsuHitObj.HitObject; + + hitCircle.BeginAbsoluteSequence(obj.StartTime - obj.TimePreempt, true); + hitCircle.ApproachCircle.ScaleTo(Scale.Value); + + hitCircle.ApproachCircle.FadeIn(Math.Min(obj.TimeFadeIn, obj.TimePreempt)); + hitCircle.ApproachCircle.ScaleTo(1f, obj.TimePreempt, BindableEasing.Value); + + hitCircle.ApproachCircle.Expire(true); + }; + }); + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 148869f5e8..64b8ca4ab1 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -150,6 +150,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModGrow(), new OsuModDeflate()), new MultiMod(new ModWindUp(), new ModWindDown()), new OsuModTraceable(), + new OsuModApproachCircle(), }; case ModType.System: From d9aef6f813d9666e18dbe313498317fea200dbb4 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Sun, 15 Mar 2020 18:53:40 +0100 Subject: [PATCH 077/520] Change label of attribute --- osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs index 42851e5cda..4e75e4b0a8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs @@ -26,14 +26,15 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Easing", "Change the easing type of the approach circles.", 0)] public Bindable BindableEasing { get; } = new Bindable(); - [SettingSource("Scale", "Change the factor of the approach circle scale.", 1)] + [SettingSource("Scale the size", "Change the factor of the approach circle scale.", 1)] + public BindableFloat Scale { get; } = new BindableFloat { Precision = 0.1f, MinValue = 2, MaxValue = 10, Default = 4, - Value = 4, + Value = 4 }; public void ApplyToDrawableHitObjects(IEnumerable drawables) From 83a4fb6f37f0fe725e52771e1d762cd407a56e2c Mon Sep 17 00:00:00 2001 From: BlauFx Date: Sun, 15 Mar 2020 19:19:09 +0100 Subject: [PATCH 078/520] Remove empty lines --- osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs index 4e75e4b0a8..0e41bef3c8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs @@ -20,14 +20,12 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Acronym => "AC"; public override string Description => "Never trust the approach circles..."; public override double ScoreMultiplier => 1; - public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle; [SettingSource("Easing", "Change the easing type of the approach circles.", 0)] public Bindable BindableEasing { get; } = new Bindable(); [SettingSource("Scale the size", "Change the factor of the approach circle scale.", 1)] - public BindableFloat Scale { get; } = new BindableFloat { Precision = 0.1f, From 8892f2c3dd2bb92d3738b122456223c30a1cea8d Mon Sep 17 00:00:00 2001 From: BlauFx Date: Sun, 15 Mar 2020 20:41:28 +0100 Subject: [PATCH 079/520] Remove redundant check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Bartłomiej Dach --- osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs index 0e41bef3c8..b227b05436 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods { drawable.ApplyCustomUpdateState += (drawableHitObj, state) => { - if (!(drawableHitObj is DrawableOsuHitObject drawableOsuHitObj) || !(drawableHitObj is DrawableHitCircle hitCircle)) return; + if (!(drawableHitObj is DrawableHitCircle hitCircle)) return; var obj = drawableOsuHitObj.HitObject; From bc705616ab62867cebc66e502cff5de82b46f5b6 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Sun, 15 Mar 2020 20:46:12 +0100 Subject: [PATCH 080/520] Fix bdach's suggestion --- osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs index b227b05436..5b5992954c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Mods { if (!(drawableHitObj is DrawableHitCircle hitCircle)) return; - var obj = drawableOsuHitObj.HitObject; + var obj = hitCircle.HitObject; hitCircle.BeginAbsoluteSequence(obj.StartTime - obj.TimePreempt, true); hitCircle.ApproachCircle.ScaleTo(Scale.Value); From 405030da471b6388d015b941a04cb466b2def9da Mon Sep 17 00:00:00 2001 From: BlauFx Date: Mon, 16 Mar 2020 20:34:53 +0100 Subject: [PATCH 081/520] Resolving bdach's requested to change the enum member names and reduce the amount of selectable easing types --- .../Mods/OsuModApproachCircle.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs index 5b5992954c..8e0175b5a3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle; [SettingSource("Easing", "Change the easing type of the approach circles.", 0)] - public Bindable BindableEasing { get; } = new Bindable(); + public Bindable BindableEasing { get; } = new Bindable(); [SettingSource("Scale the size", "Change the factor of the approach circle scale.", 1)] public BindableFloat Scale { get; } = new BindableFloat @@ -49,11 +49,25 @@ namespace osu.Game.Rulesets.Osu.Mods hitCircle.ApproachCircle.ScaleTo(Scale.Value); hitCircle.ApproachCircle.FadeIn(Math.Min(obj.TimeFadeIn, obj.TimePreempt)); - hitCircle.ApproachCircle.ScaleTo(1f, obj.TimePreempt, BindableEasing.Value); + + hitCircle.ApproachCircle.ScaleTo(1f, obj.TimePreempt, (Easing)BindableEasing.Value); hitCircle.ApproachCircle.Expire(true); }; }); } + + internal enum ReadableEasing + { + Accelerate = 2, + Accelerate2 = 6, + Accelerate3 = 12, + AccelerateInAfterDeceleraingeOut = 29, + CasualBounces = 32, + CasualBouncesWhileAccelerating = 24, + Decelerate = 1, + DecelerateAfterAccelerating = 8, + Default = 0, + } } } From ee103c9a1e4804744bed343d7a647f2d8e6a023f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jan 2021 16:59:01 +0900 Subject: [PATCH 082/520] Add prerelease realm package --- osu.Game/osu.Game.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f28a55e016..eeacb10d14 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -28,6 +28,7 @@ + From dce9937e9b93898aa28fffe227ad45578f021e7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jan 2021 14:19:14 +0900 Subject: [PATCH 083/520] Add automapper for detaching support --- osu.Game/osu.Game.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index eeacb10d14..1a762be9c9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,6 +18,7 @@ + From 9cfede2e7e57305d6916b058eef96bf19de254f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jan 2021 14:07:36 +0900 Subject: [PATCH 084/520] Setup context, write usage, wrapper classes --- osu.Game/Database/IHasGuidPrimaryKey.cs | 16 ++ osu.Game/Database/IRealmFactory.cs | 19 ++ osu.Game/Database/RealmBackedStore.cs | 27 +++ osu.Game/Database/RealmContextFactory.cs | 262 +++++++++++++++++++++++ osu.Game/Database/RealmWriteUsage.cs | 55 +++++ osu.Game/OsuGameBase.cs | 4 + 6 files changed, 383 insertions(+) create mode 100644 osu.Game/Database/IHasGuidPrimaryKey.cs create mode 100644 osu.Game/Database/IRealmFactory.cs create mode 100644 osu.Game/Database/RealmBackedStore.cs create mode 100644 osu.Game/Database/RealmContextFactory.cs create mode 100644 osu.Game/Database/RealmWriteUsage.cs diff --git a/osu.Game/Database/IHasGuidPrimaryKey.cs b/osu.Game/Database/IHasGuidPrimaryKey.cs new file mode 100644 index 0000000000..3dda32a5b0 --- /dev/null +++ b/osu.Game/Database/IHasGuidPrimaryKey.cs @@ -0,0 +1,16 @@ +// 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.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + +namespace osu.Game.Database +{ + public interface IHasGuidPrimaryKey + { + [JsonIgnore] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + Guid ID { get; set; } + } +} diff --git a/osu.Game/Database/IRealmFactory.cs b/osu.Game/Database/IRealmFactory.cs new file mode 100644 index 0000000000..d65bcaebbe --- /dev/null +++ b/osu.Game/Database/IRealmFactory.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Realms; + +namespace osu.Game.Database +{ + public interface IRealmFactory + { + public Realm Get() => Realm.GetInstance(); + + /// + /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). + /// This method may block if a write is already active on a different thread. + /// + /// A usage containing a usable context. + RealmWriteUsage GetForWrite(); + } +} diff --git a/osu.Game/Database/RealmBackedStore.cs b/osu.Game/Database/RealmBackedStore.cs new file mode 100644 index 0000000000..e37831d9d5 --- /dev/null +++ b/osu.Game/Database/RealmBackedStore.cs @@ -0,0 +1,27 @@ +// 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.Platform; + +namespace osu.Game.Database +{ + public abstract class RealmBackedStore + { + protected readonly Storage Storage; + + protected readonly IRealmFactory ContextFactory; + + protected RealmBackedStore(IRealmFactory contextFactory, Storage storage = null) + { + ContextFactory = contextFactory; + Storage = storage; + } + + /// + /// Perform any common clean-up tasks. Should be run when idle, or whenever necessary. + /// + public virtual void Cleanup() + { + } + } +} diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs new file mode 100644 index 0000000000..826e098669 --- /dev/null +++ b/osu.Game/Database/RealmContextFactory.cs @@ -0,0 +1,262 @@ +// 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.CodeAnalysis; +using System.Threading; +using AutoMapper; +using osu.Framework.Platform; +using osu.Framework.Statistics; +using osu.Framework.Threading; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Input.Bindings; +using osu.Game.IO; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Skinning; +using Realms; + +namespace osu.Game.Database +{ + public class RealmContextFactory : IRealmFactory + { + private readonly Storage storage; + private readonly Scheduler scheduler; + + private const string database_name = @"client"; + + private ThreadLocal threadContexts; + + private readonly object writeLock = new object(); + + private ThreadLocal refreshCompleted = new ThreadLocal(); + + private bool rollbackRequired; + + private int currentWriteUsages; + + private Transaction currentWriteTransaction; + + public RealmContextFactory(Storage storage, Scheduler scheduler) + { + this.storage = storage; + this.scheduler = scheduler; + recreateThreadContexts(); + } + + private static readonly GlobalStatistic reads = GlobalStatistics.Get("Database", "Get (Read)"); + private static readonly GlobalStatistic writes = GlobalStatistics.Get("Database", "Get (Write)"); + private static readonly GlobalStatistic commits = GlobalStatistics.Get("Database", "Commits"); + private static readonly GlobalStatistic rollbacks = GlobalStatistics.Get("Database", "Rollbacks"); + private static readonly GlobalStatistic contexts = GlobalStatistics.Get("Database", "Contexts"); + private Thread writingThread; + + /// + /// Get a context for the current thread for read-only usage. + /// If a is in progress, the existing write-safe context will be returned. + /// + public Realm Get() + { + reads.Value++; + return getContextForCurrentThread(); + } + + /// + /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). + /// This method may block if a write is already active on a different thread. + /// + /// A usage containing a usable context. + public RealmWriteUsage GetForWrite() + { + writes.Value++; + Monitor.Enter(writeLock); + Realm context; + + try + { + context = getContextForCurrentThread(); + + if (currentWriteTransaction == null) + { + writingThread = Thread.CurrentThread; + currentWriteTransaction = context.BeginWrite(); + } + } + catch + { + // retrieval of a context could trigger a fatal error. + Monitor.Exit(writeLock); + throw; + } + + Interlocked.Increment(ref currentWriteUsages); + + return new RealmWriteUsage(context, usageCompleted) { IsTransactionLeader = currentWriteTransaction != null && currentWriteUsages == 1 }; + } + + // TODO: remove if not necessary. + public void Schedule(Action action) => scheduler.Add(action); + + private Realm getContextForCurrentThread() + { + var context = threadContexts.Value; + if (context?.IsClosed != false) + threadContexts.Value = context = CreateContext(); + + if (!refreshCompleted.Value) + { + context.Refresh(); + refreshCompleted.Value = true; + } + + return context; + } + + private void usageCompleted(RealmWriteUsage usage) + { + int usages = Interlocked.Decrement(ref currentWriteUsages); + + try + { + rollbackRequired |= usage.RollbackRequired; + + if (usages == 0) + { + if (rollbackRequired) + { + rollbacks.Value++; + currentWriteTransaction?.Rollback(); + } + else + { + commits.Value++; + currentWriteTransaction?.Commit(); + } + + currentWriteTransaction = null; + writingThread = null; + rollbackRequired = false; + + refreshCompleted = new ThreadLocal(); + } + } + finally + { + Monitor.Exit(writeLock); + } + } + + private void recreateThreadContexts() + { + // Contexts for other threads are not disposed as they may be in use elsewhere. Instead, fresh contexts are exposed + // for other threads to use, and we rely on the finalizer inside OsuDbContext to handle their previous contexts + threadContexts?.Value.Dispose(); + threadContexts = new ThreadLocal(CreateContext, true); + } + + protected virtual Realm CreateContext() + { + contexts.Value++; + return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true))); + } + + public void ResetDatabase() + { + lock (writeLock) + { + recreateThreadContexts(); + storage.DeleteDatabase(database_name); + } + } + } + + [SuppressMessage("ReSharper", "CA2225")] + public class RealmWrapper : IEquatable> + where T : RealmObject, IHasGuidPrimaryKey + { + public Guid ID { get; } + + private readonly ThreadLocal threadValues; + + public readonly IRealmFactory ContextFactory; + + public RealmWrapper(T original, IRealmFactory contextFactory) + { + ContextFactory = contextFactory; + ID = original.ID; + + var originalContext = original.Realm; + + threadValues = new ThreadLocal(() => + { + var context = ContextFactory?.Get(); + + if (context == null || originalContext?.IsSameInstance(context) != false) + return original; + + return context.Find(ID); + }); + } + + public T Get() => threadValues.Value; + + public RealmWrapper WrapChild(Func lookup) + where TChild : RealmObject, IHasGuidPrimaryKey => new RealmWrapper(lookup(Get()), ContextFactory); + + // ReSharper disable once CA2225 + public static implicit operator T(RealmWrapper wrapper) + => wrapper?.Get().Detach(); + + // ReSharper disable once CA2225 + public static implicit operator RealmWrapper(T obj) => obj.WrapAsUnmanaged(); + + public bool Equals(RealmWrapper other) => other != null && other.ID == ID; + + public override string ToString() => Get().ToString(); + } + + public static class RealmExtensions + { + private static readonly IMapper mapper = new MapperConfiguration(c => + { + c.ShouldMapField = fi => false; + c.ShouldMapProperty = pi => pi.SetMethod != null && pi.SetMethod.IsPublic; + + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + + c.CreateMap() + .ForMember(s => s.Beatmaps, d => d.MapFrom(s => s.Beatmaps)) + .ForMember(s => s.Files, d => d.MapFrom(s => s.Files)) + .MaxDepth(2); + + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + }).CreateMapper(); + + public static T Detach(this T obj) where T : RealmObject + { + if (!obj.IsManaged) + return obj; + + var detached = mapper.Map(obj); + + //typeof(RealmObject).GetField("_realm", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.SetValue(detached, null); + + return detached; + } + + public static RealmWrapper Wrap(this T obj, IRealmFactory contextFactory) + where T : RealmObject, IHasGuidPrimaryKey => new RealmWrapper(obj, contextFactory); + + public static RealmWrapper WrapAsUnmanaged(this T obj) + where T : RealmObject, IHasGuidPrimaryKey => new RealmWrapper(obj, null); + } +} diff --git a/osu.Game/Database/RealmWriteUsage.cs b/osu.Game/Database/RealmWriteUsage.cs new file mode 100644 index 0000000000..35e30e8123 --- /dev/null +++ b/osu.Game/Database/RealmWriteUsage.cs @@ -0,0 +1,55 @@ +// 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 Realms; + +namespace osu.Game.Database +{ + public class RealmWriteUsage : IDisposable + { + public readonly Realm Context; + private readonly Action usageCompleted; + + public bool RollbackRequired { get; private set; } + + public RealmWriteUsage(Realm context, Action onCompleted) + { + Context = context; + usageCompleted = onCompleted; + } + + /// + /// Whether this write usage will commit a transaction on completion. + /// If false, there is a parent usage responsible for transaction commit. + /// + public bool IsTransactionLeader; + + private bool isDisposed; + + protected void Dispose(bool disposing) + { + if (isDisposed) return; + + isDisposed = true; + + usageCompleted?.Invoke(this); + } + + public void Rollback(Exception error = null) + { + RollbackRequired = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~RealmWriteUsage() + { + Dispose(false); + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0b5abc4e31..8ec6976c63 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -146,6 +146,8 @@ namespace osu.Game private DatabaseContextFactory contextFactory; + private RealmContextFactory realmFactory; + protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); [BackgroundDependencyLoader] @@ -167,6 +169,8 @@ namespace osu.Game dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage)); + dependencies.Cache(realmFactory = new RealmContextFactory(Storage, Scheduler)); + dependencies.CacheAs(Storage); var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures"))); From 5372d95d167953e812d25941d5fd44e46e47e36c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jan 2021 14:29:22 +0900 Subject: [PATCH 085/520] Initialise FodyWeavers --- osu.Game/FodyWeavers.xml | 3 +++ osu.Game/FodyWeavers.xsd | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 osu.Game/FodyWeavers.xml create mode 100644 osu.Game/FodyWeavers.xsd diff --git a/osu.Game/FodyWeavers.xml b/osu.Game/FodyWeavers.xml new file mode 100644 index 0000000000..cc07b89533 --- /dev/null +++ b/osu.Game/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/osu.Game/FodyWeavers.xsd b/osu.Game/FodyWeavers.xsd new file mode 100644 index 0000000000..f526bddb09 --- /dev/null +++ b/osu.Game/FodyWeavers.xsd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file From d5ac97ece849f46711e97e5e0e290119a4370ef2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jan 2021 14:35:15 +0900 Subject: [PATCH 086/520] Add realm store / key binding implementations --- osu.Game/Input/Bindings/RealmKeyBinding.cs | 33 ++++++++ osu.Game/Input/RealmKeyBindingStore.cs | 92 ++++++++++++++++++++++ osu.Game/OsuGameBase.cs | 1 + 3 files changed, 126 insertions(+) create mode 100644 osu.Game/Input/Bindings/RealmKeyBinding.cs create mode 100644 osu.Game/Input/RealmKeyBindingStore.cs diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs new file mode 100644 index 0000000000..a8cd1c3fb6 --- /dev/null +++ b/osu.Game/Input/Bindings/RealmKeyBinding.cs @@ -0,0 +1,33 @@ +// 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.Input.Bindings; +using osu.Game.Database; +using Realms; + +namespace osu.Game.Input.Bindings +{ + [MapTo("KeyBinding")] + public class RealmKeyBinding : RealmObject, IHasGuidPrimaryKey + { + public Guid ID { get; set; } + + public int? RulesetID { get; set; } + + public int? Variant { get; set; } + + [Ignored] + public KeyBinding KeyBinding + { + get + { + var split = KeyBindingString.Split(':'); + return new KeyBinding(split[0], int.Parse(split[1])); + } + set => KeyBindingString = $"{value.KeyCombination}:{(int)value.Action}"; + } + + public string KeyBindingString { get; set; } + } +} diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs new file mode 100644 index 0000000000..471a25dd0d --- /dev/null +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -0,0 +1,92 @@ +// 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.Linq; +using osu.Framework.Input.Bindings; +using osu.Framework.Platform; +using osu.Game.Database; +using osu.Game.Input.Bindings; +using osu.Game.Rulesets; + +namespace osu.Game.Input +{ + public class RealmKeyBindingStore : RealmBackedStore + { + public event Action KeyBindingChanged; + + public RealmKeyBindingStore(RealmContextFactory contextFactory, RulesetStore rulesets, Storage storage = null) + : base(contextFactory, storage) + { + using (ContextFactory.GetForWrite()) + { + foreach (RulesetInfo info in rulesets.AvailableRulesets) + { + var ruleset = info.CreateInstance(); + foreach (var variant in ruleset.AvailableVariants) + insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant); + } + } + } + + public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings); + + private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) + { + using (var usage = ContextFactory.GetForWrite()) + { + // compare counts in database vs defaults + foreach (var group in defaults.GroupBy(k => k.Action)) + { + int count = Query(rulesetId, variant).Count(k => k.KeyBinding.Action == group.Key); + int aimCount = group.Count(); + + if (aimCount <= count) + continue; + + foreach (var insertable in group.Skip(count).Take(aimCount - count)) + { + // insert any defaults which are missing. + usage.Context.Add(new RealmKeyBinding + { + KeyBinding = new KeyBinding + { + KeyCombination = insertable.KeyCombination, + Action = insertable.Action, + }, + RulesetID = rulesetId, + Variant = variant + }); + } + } + } + } + + /// + /// Retrieve s for a specified ruleset/variant content. + /// + /// The ruleset's internal ID. + /// An optional variant. + /// + public List Query(int? rulesetId = null, int? variant = null) => + ContextFactory.Get().All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); + + public void Update(KeyBinding keyBinding) + { + using (ContextFactory.GetForWrite()) + { + //todo: fix + // var dbKeyBinding = (RealmKeyBinding)keyBinding; + // Refresh(ref dbKeyBinding); + // + // if (dbKeyBinding.KeyCombination.Equals(keyBinding.KeyCombination)) + // return; + // + // dbKeyBinding.KeyCombination = keyBinding.KeyCombination; + } + + KeyBindingChanged?.Invoke(); + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8ec6976c63..95b1d3100c 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -266,6 +266,7 @@ namespace osu.Game AddInternal(scorePerformanceManager); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); + dependencies.Cache(new RealmKeyBindingStore(realmFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); dependencies.Cache(new SessionStatics()); From 5d7ab4a7f12b7a07b4b2b1779908512307433f1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jan 2021 15:01:41 +0900 Subject: [PATCH 087/520] Rename global statistics to be specific to realm --- osu.Game/Database/RealmContextFactory.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 826e098669..c37068947e 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -45,11 +45,11 @@ namespace osu.Game.Database recreateThreadContexts(); } - private static readonly GlobalStatistic reads = GlobalStatistics.Get("Database", "Get (Read)"); - private static readonly GlobalStatistic writes = GlobalStatistics.Get("Database", "Get (Write)"); - private static readonly GlobalStatistic commits = GlobalStatistics.Get("Database", "Commits"); - private static readonly GlobalStatistic rollbacks = GlobalStatistics.Get("Database", "Rollbacks"); - private static readonly GlobalStatistic contexts = GlobalStatistics.Get("Database", "Contexts"); + private static readonly GlobalStatistic reads = GlobalStatistics.Get("Realm", "Get (Read)"); + private static readonly GlobalStatistic writes = GlobalStatistics.Get("Realm", "Get (Write)"); + private static readonly GlobalStatistic commits = GlobalStatistics.Get("Realm", "Commits"); + private static readonly GlobalStatistic rollbacks = GlobalStatistics.Get("Realm", "Rollbacks"); + private static readonly GlobalStatistic contexts = GlobalStatistics.Get("Realm", "Contexts"); private Thread writingThread; /// From ae76eca5648525abf580b4dfa6ebc78eb69fd423 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jan 2021 15:41:29 +0900 Subject: [PATCH 088/520] Add basic realm migration support --- osu.Game/Database/RealmContextFactory.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index c37068947e..b918eb0e78 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -43,6 +43,15 @@ namespace osu.Game.Database this.storage = storage; this.scheduler = scheduler; recreateThreadContexts(); + + using (CreateContext()) + { + // ensure our schema is up-to-date and migrated. + } + } + + private void onMigration(Migration migration, ulong oldschemaversion) + { } private static readonly GlobalStatistic reads = GlobalStatistics.Get("Realm", "Get (Read)"); @@ -158,7 +167,11 @@ namespace osu.Game.Database protected virtual Realm CreateContext() { contexts.Value++; - return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true))); + return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) + { + SchemaVersion = 2, + MigrationCallback = onMigration + }); } public void ResetDatabase() From 845d5cdea23bf0fc179b3a1226c109d2cfbd6b94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jan 2021 15:41:58 +0900 Subject: [PATCH 089/520] Switch guid to store as string until fody issues are resolved See https://github.com/realm/realm-dotnet/issues/740#issuecomment-755898968 --- osu.Game/Database/IHasGuidPrimaryKey.cs | 11 ++++++++++- osu.Game/Database/RealmContextFactory.cs | 2 +- osu.Game/Input/Bindings/RealmKeyBinding.cs | 3 +-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/IHasGuidPrimaryKey.cs b/osu.Game/Database/IHasGuidPrimaryKey.cs index 3dda32a5b0..33618e990d 100644 --- a/osu.Game/Database/IHasGuidPrimaryKey.cs +++ b/osu.Game/Database/IHasGuidPrimaryKey.cs @@ -4,13 +4,22 @@ using System; using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; +using Realms; namespace osu.Game.Database { public interface IHasGuidPrimaryKey { + [JsonIgnore] + [Ignored] + public Guid Guid + { + get => new Guid(ID); + set => ID = value.ToString(); + } + [JsonIgnore] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - Guid ID { get; set; } + string ID { get; set; } } } diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index b918eb0e78..feb03c1609 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -197,7 +197,7 @@ namespace osu.Game.Database public RealmWrapper(T original, IRealmFactory contextFactory) { ContextFactory = contextFactory; - ID = original.ID; + ID = original.Guid; var originalContext = original.Realm; diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs index a8cd1c3fb6..332e4e2b21 100644 --- a/osu.Game/Input/Bindings/RealmKeyBinding.cs +++ b/osu.Game/Input/Bindings/RealmKeyBinding.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 System; using osu.Framework.Input.Bindings; using osu.Game.Database; using Realms; @@ -11,7 +10,7 @@ namespace osu.Game.Input.Bindings [MapTo("KeyBinding")] public class RealmKeyBinding : RealmObject, IHasGuidPrimaryKey { - public Guid ID { get; set; } + public string ID { get; set; } public int? RulesetID { get; set; } From ee6a26bd6eff8988b30d061eb4b2bcbbaddbd755 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jan 2021 15:42:21 +0900 Subject: [PATCH 090/520] Initialise new key bindings with a primary key --- osu.Game/Input/RealmKeyBindingStore.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 471a25dd0d..752e254a43 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -50,6 +50,7 @@ namespace osu.Game.Input // insert any defaults which are missing. usage.Context.Add(new RealmKeyBinding { + ID = Guid.NewGuid().ToString(), KeyBinding = new KeyBinding { KeyCombination = insertable.KeyCombination, From 391259c713cb9b8303839ea4872c42f4345ea197 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jan 2021 15:51:16 +0900 Subject: [PATCH 091/520] Add missing implementation details to realm keybinding store --- osu.Game/Input/RealmKeyBindingStore.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 752e254a43..f81d701e62 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -30,6 +30,23 @@ namespace osu.Game.Input } } + /// + /// Retrieve all user-defined key combinations (in a format that can be displayed) for a specific action. + /// + /// The action to lookup. + /// A set of display strings for all the user's key configuration for the action. + public IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction) + { + foreach (var action in Query().Where(b => (GlobalAction)b.KeyBinding.Action == globalAction)) + { + string str = action.KeyBinding.KeyCombination.ReadableString(); + + // even if found, the readable string may be empty for an unbound action. + if (str.Length > 0) + yield return str; + } + } + public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings); private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) From 382a40b24375c328ec356cf802cc37b67a30e2a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jan 2021 15:51:29 +0900 Subject: [PATCH 092/520] Tidy up some missed inspections in RealmContextFactory --- osu.Game/Database/RealmContextFactory.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index feb03c1609..8e1a0bb8f7 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -59,7 +59,6 @@ namespace osu.Game.Database private static readonly GlobalStatistic commits = GlobalStatistics.Get("Realm", "Commits"); private static readonly GlobalStatistic rollbacks = GlobalStatistics.Get("Realm", "Rollbacks"); private static readonly GlobalStatistic contexts = GlobalStatistics.Get("Realm", "Contexts"); - private Thread writingThread; /// /// Get a context for the current thread for read-only usage. @@ -86,11 +85,7 @@ namespace osu.Game.Database { context = getContextForCurrentThread(); - if (currentWriteTransaction == null) - { - writingThread = Thread.CurrentThread; - currentWriteTransaction = context.BeginWrite(); - } + currentWriteTransaction ??= context.BeginWrite(); } catch { @@ -144,7 +139,6 @@ namespace osu.Game.Database } currentWriteTransaction = null; - writingThread = null; rollbackRequired = false; refreshCompleted = new ThreadLocal(); From a9a3a959914cb288dec68dedb74f2b1236c9b23d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jan 2021 15:51:38 +0900 Subject: [PATCH 093/520] Replace KeybindingStore with realm version --- osu.Game/OsuGameBase.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 95b1d3100c..22e72d9f36 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -73,7 +73,7 @@ namespace osu.Game protected FileStore FileStore; - protected KeyBindingStore KeyBindingStore; + protected RealmKeyBindingStore KeyBindingStore; protected SettingsStore SettingsStore; @@ -265,8 +265,10 @@ namespace osu.Game dependencies.Cache(scorePerformanceManager); AddInternal(scorePerformanceManager); - dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); - dependencies.Cache(new RealmKeyBindingStore(realmFactory, RulesetStore)); + // todo: migrate to realm + // dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); + + dependencies.Cache(KeyBindingStore = new RealmKeyBindingStore(realmFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); dependencies.Cache(new SessionStatics()); From 43f417b53ab036b267dc4f59eab046085f3f1493 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jan 2021 16:38:49 +0900 Subject: [PATCH 094/520] Add and consume IKeyBindingStore interface --- osu.Game.Tests/Visual/TestSceneOsuGame.cs | 2 +- .../Bindings/DatabasedKeyBindingContainer.cs | 12 ++---- osu.Game/Input/IKeyBindingStore.cs | 40 +++++++++++++++++++ osu.Game/Input/KeyBindingStore.cs | 14 +++++-- osu.Game/Input/RealmKeyBindingStore.cs | 14 +++++-- osu.Game/OsuGameBase.cs | 4 +- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 2 +- .../KeyBinding/KeyBindingsSubsection.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 16 ++++++-- 9 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 osu.Game/Input/IKeyBindingStore.cs diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs index b347c39c1e..eddaf36f92 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual typeof(FileStore), typeof(ScoreManager), typeof(BeatmapManager), - typeof(KeyBindingStore), + typeof(IKeyBindingStore), typeof(SettingsStore), typeof(RulesetConfigCache), typeof(OsuColour), diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 94edc33099..edaf18a760 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Rulesets; -using System.Linq; namespace osu.Game.Input.Bindings { @@ -21,7 +20,8 @@ namespace osu.Game.Input.Bindings private readonly int? variant; - private KeyBindingStore store; + [Resolved] + private IKeyBindingStore store { get; set; } public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(variant ?? 0); @@ -42,12 +42,6 @@ namespace osu.Game.Input.Bindings throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); } - [BackgroundDependencyLoader] - private void load(KeyBindingStore keyBindings) - { - store = keyBindings; - } - protected override void LoadComplete() { base.LoadComplete(); @@ -69,7 +63,7 @@ namespace osu.Game.Input.Bindings // fallback to defaults instead. KeyBindings = DefaultKeyBindings; else - KeyBindings = store.Query(ruleset?.ID, variant).ToList(); + KeyBindings = store.Query(ruleset?.ID, variant); } } } diff --git a/osu.Game/Input/IKeyBindingStore.cs b/osu.Game/Input/IKeyBindingStore.cs new file mode 100644 index 0000000000..3574c7237f --- /dev/null +++ b/osu.Game/Input/IKeyBindingStore.cs @@ -0,0 +1,40 @@ +// 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 osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; + +namespace osu.Game.Input +{ + public interface IKeyBindingStore + { + event Action KeyBindingChanged; + + void Register(KeyBindingContainer manager); + + /// + /// Retrieve all user-defined key combinations (in a format that can be displayed) for a specific action. + /// + /// The action to lookup. + /// A set of display strings for all the user's key configuration for the action. + IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction); + + /// + /// Retrieve s for a specified ruleset/variant content. + /// + /// The ruleset's internal ID. + /// An optional variant. + /// + List Query(int? rulesetId = null, int? variant = null); + + /// + /// Retrieve s for the specified action. + /// + /// The action to lookup. + List Query(GlobalAction action); + + public void Update(KeyBinding buttonKeyBinding) => throw new NotImplementedException(); + } +} diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index bc73d74d74..bbf26c4d8f 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets; namespace osu.Game.Input { - public class KeyBindingStore : DatabaseBackedStore + public class KeyBindingStore : DatabaseBackedStore, IKeyBindingStore { public event Action KeyBindingChanged; @@ -39,7 +39,7 @@ namespace osu.Game.Input /// A set of display strings for all the user's key configuration for the action. public IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction) { - foreach (var action in Query().Where(b => (GlobalAction)b.Action == globalAction)) + foreach (var action in query().Where(b => (GlobalAction)b.Action == globalAction)) { string str = action.KeyCombination.ReadableString(); @@ -49,6 +49,12 @@ namespace osu.Game.Input } } + public List Query(int? rulesetId = null, int? variant = null) + => query(rulesetId, variant).OfType().ToList(); + + public List Query(GlobalAction action) + => query(null, null).Where(dkb => (GlobalAction)dkb.Action == action).OfType().ToList(); + private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) { using (var usage = ContextFactory.GetForWrite()) @@ -56,7 +62,7 @@ namespace osu.Game.Input // compare counts in database vs defaults foreach (var group in defaults.GroupBy(k => k.Action)) { - int count = Query(rulesetId, variant).Count(k => (int)k.Action == (int)group.Key); + int count = query(rulesetId, variant).Count(k => (int)k.Action == (int)group.Key); int aimCount = group.Count(); if (aimCount <= count) @@ -86,7 +92,7 @@ namespace osu.Game.Input /// The ruleset's internal ID. /// An optional variant. /// - public List Query(int? rulesetId = null, int? variant = null) => + private List query(int? rulesetId = null, int? variant = null) => ContextFactory.Get().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); public void Update(KeyBinding keyBinding) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index f81d701e62..07a340b25c 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets; namespace osu.Game.Input { - public class RealmKeyBindingStore : RealmBackedStore + public class RealmKeyBindingStore : RealmBackedStore, IKeyBindingStore { public event Action KeyBindingChanged; @@ -37,7 +37,7 @@ namespace osu.Game.Input /// A set of display strings for all the user's key configuration for the action. public IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction) { - foreach (var action in Query().Where(b => (GlobalAction)b.KeyBinding.Action == globalAction)) + foreach (var action in query().Where(b => (GlobalAction)b.KeyBinding.Action == globalAction)) { string str = action.KeyBinding.KeyCombination.ReadableString(); @@ -56,7 +56,7 @@ namespace osu.Game.Input // compare counts in database vs defaults foreach (var group in defaults.GroupBy(k => k.Action)) { - int count = Query(rulesetId, variant).Count(k => k.KeyBinding.Action == group.Key); + int count = query(rulesetId, variant).Count(k => (int)k.KeyBinding.Action == (int)group.Key); int aimCount = group.Count(); if (aimCount <= count) @@ -87,9 +87,15 @@ namespace osu.Game.Input /// The ruleset's internal ID. /// An optional variant. /// - public List Query(int? rulesetId = null, int? variant = null) => + private List query(int? rulesetId = null, int? variant = null) => ContextFactory.Get().All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); + public List Query(int? rulesetId = null, int? variant = null) + => query(rulesetId, variant).Select(k => k.KeyBinding).ToList(); + + public List Query(GlobalAction action) + => query(null, null).Where(rkb => rkb.KeyBindingString.StartsWith($"{(int)action}:", StringComparison.Ordinal)).Select(k => k.KeyBinding).ToList(); + public void Update(KeyBinding keyBinding) { using (ContextFactory.GetForWrite()) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 22e72d9f36..95e1bc69b3 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -73,7 +73,7 @@ namespace osu.Game protected FileStore FileStore; - protected RealmKeyBindingStore KeyBindingStore; + protected IKeyBindingStore KeyBindingStore; protected SettingsStore SettingsStore; @@ -268,7 +268,7 @@ namespace osu.Game // todo: migrate to realm // dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); - dependencies.Cache(KeyBindingStore = new RealmKeyBindingStore(realmFactory, RulesetStore)); + dependencies.CacheAs(KeyBindingStore = new RealmKeyBindingStore(realmFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); dependencies.Cache(new SessionStatics()); diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index b808d49fa2..d9f63328d0 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.KeyBinding } [Resolved] - private KeyBindingStore store { get; set; } + private IKeyBindingStore store { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index d784b7aec9..b5d6bc98c3 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.KeyBinding } [BackgroundDependencyLoader] - private void load(KeyBindingStore store) + private void load(IKeyBindingStore store) { var bindings = store.Query(Ruleset?.ID, variant); diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 49b9c62d85..747f5e9bd0 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.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.Linq; using osu.Framework.Allocation; using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; @@ -74,7 +75,7 @@ namespace osu.Game.Overlays.Toolbar protected FillFlowContainer Flow; [Resolved] - private KeyBindingStore keyBindings { get; set; } + private IKeyBindingStore keyBindings { get; set; } protected ToolbarButton() : base(HoverSampleSet.Loud) @@ -171,9 +172,16 @@ namespace osu.Game.Overlays.Toolbar if (tooltipKeyBinding.IsValid) return; - var binding = keyBindings.Query().Find(b => (GlobalAction)b.Action == Hotkey); - var keyBindingString = binding?.KeyCombination.ReadableString(); - keyBindingTooltip.Text = !string.IsNullOrEmpty(keyBindingString) ? $" ({keyBindingString})" : string.Empty; + keyBindingTooltip.Text = string.Empty; + + if (Hotkey != null) + { + KeyCombination? binding = keyBindings.Query(Hotkey.Value).FirstOrDefault()?.KeyCombination; + var keyBindingString = binding?.ReadableString(); + + if (!string.IsNullOrEmpty(keyBindingString)) + keyBindingTooltip.Text = $" ({keyBindingString})"; + } tooltipKeyBinding.Validate(); } From a77519c6bde35c3b24e0e1888ec7059e1f149555 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jan 2021 16:53:36 +0900 Subject: [PATCH 095/520] Store KeyBinding action to its own field in realm Also improve the Query method for action types by using generic field --- osu.Game/Database/RealmContextFactory.cs | 2 +- osu.Game/Input/Bindings/RealmKeyBinding.cs | 14 ++++++++------ osu.Game/Input/IKeyBindingStore.cs | 2 +- osu.Game/Input/KeyBindingStore.cs | 7 +++++-- osu.Game/Input/RealmKeyBindingStore.cs | 9 +++++++-- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 8e1a0bb8f7..2f6ccb8911 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -163,7 +163,7 @@ namespace osu.Game.Database contexts.Value++; return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) { - SchemaVersion = 2, + SchemaVersion = 3, MigrationCallback = onMigration }); } diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs index 332e4e2b21..eb04766d04 100644 --- a/osu.Game/Input/Bindings/RealmKeyBinding.cs +++ b/osu.Game/Input/Bindings/RealmKeyBinding.cs @@ -16,17 +16,19 @@ namespace osu.Game.Input.Bindings public int? Variant { get; set; } + public int Action { get; set; } + + public string KeyCombination { get; set; } + [Ignored] public KeyBinding KeyBinding { - get + get => new KeyBinding(KeyCombination, Action); + set { - var split = KeyBindingString.Split(':'); - return new KeyBinding(split[0], int.Parse(split[1])); + KeyCombination = value.KeyCombination.ToString(); + Action = (int)value.Action; } - set => KeyBindingString = $"{value.KeyCombination}:{(int)value.Action}"; } - - public string KeyBindingString { get; set; } } } diff --git a/osu.Game/Input/IKeyBindingStore.cs b/osu.Game/Input/IKeyBindingStore.cs index 3574c7237f..c5e68dc6ca 100644 --- a/osu.Game/Input/IKeyBindingStore.cs +++ b/osu.Game/Input/IKeyBindingStore.cs @@ -33,7 +33,7 @@ namespace osu.Game.Input /// Retrieve s for the specified action. /// /// The action to lookup. - List Query(GlobalAction action); + List Query(T action) where T : Enum; public void Update(KeyBinding buttonKeyBinding) => throw new NotImplementedException(); } diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index bbf26c4d8f..53eb0024f8 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -52,8 +52,11 @@ namespace osu.Game.Input public List Query(int? rulesetId = null, int? variant = null) => query(rulesetId, variant).OfType().ToList(); - public List Query(GlobalAction action) - => query(null, null).Where(dkb => (GlobalAction)dkb.Action == action).OfType().ToList(); + public List Query(T action) where T : Enum + { + int lookup = (int)(object)action; + return query(null, null).Where(rkb => (int)rkb.Action == lookup).OfType().ToList(); + } private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) { diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 07a340b25c..37d0ce18ed 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -93,8 +93,13 @@ namespace osu.Game.Input public List Query(int? rulesetId = null, int? variant = null) => query(rulesetId, variant).Select(k => k.KeyBinding).ToList(); - public List Query(GlobalAction action) - => query(null, null).Where(rkb => rkb.KeyBindingString.StartsWith($"{(int)action}:", StringComparison.Ordinal)).Select(k => k.KeyBinding).ToList(); + public List Query(T action) + where T : Enum + { + int lookup = (int)(object)action; + + return query(null, null).Where(rkb => rkb.Action == lookup).Select(k => k.KeyBinding).ToList(); + } public void Update(KeyBinding keyBinding) { From 8765aaf9e669a88eacd31089671bfac20fd31e66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jan 2021 15:49:01 +0900 Subject: [PATCH 096/520] Use IKeyBinding for all key binding usages (and add update flow via primary key) --- .../Gameplay/TestSceneReplayRecorder.cs | 2 +- .../Gameplay/TestSceneReplayRecording.cs | 2 +- .../Gameplay/TestSceneSpectatorPlayback.cs | 2 +- .../Input/Bindings/DatabasedKeyBinding.cs | 24 ++++++++----- .../Bindings/DatabasedKeyBindingContainer.cs | 2 +- .../Input/Bindings/GlobalActionContainer.cs | 2 +- osu.Game/Input/Bindings/RealmKeyBinding.cs | 15 +++++++- osu.Game/Input/IKeyBindingStore.cs | 9 ++--- osu.Game/Input/KeyBindingStore.cs | 19 +++++++---- osu.Game/Input/RealmKeyBindingStore.cs | 34 ++++++++----------- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 13 +++---- 11 files changed, 72 insertions(+), 52 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index b2ad7ca5b4..802dbf2021 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -244,7 +244,7 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestKeyBindingContainer : KeyBindingContainer { - public override IEnumerable DefaultKeyBindings => new[] + public override IEnumerable DefaultKeyBindings => new[] { new KeyBinding(InputKey.MouseLeft, TestAction.Down), }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index 40c4214749..6e338b7202 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestKeyBindingContainer : KeyBindingContainer { - public override IEnumerable DefaultKeyBindings => new[] + public override IEnumerable DefaultKeyBindings => new[] { new KeyBinding(InputKey.MouseLeft, TestAction.Down), }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index e148fa381c..a5fd5afcde 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -298,7 +298,7 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestKeyBindingContainer : KeyBindingContainer { - public override IEnumerable DefaultKeyBindings => new[] + public override IEnumerable DefaultKeyBindings => new[] { new KeyBinding(InputKey.MouseLeft, TestAction.Down), }; diff --git a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs index 8c0072c3da..ad3493d0fc 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs @@ -8,7 +8,7 @@ using osu.Game.Database; namespace osu.Game.Input.Bindings { [Table("KeyBinding")] - public class DatabasedKeyBinding : KeyBinding, IHasPrimaryKey + public class DatabasedKeyBinding : IKeyBinding, IHasPrimaryKey { public int ID { get; set; } @@ -17,17 +17,23 @@ namespace osu.Game.Input.Bindings public int? Variant { get; set; } [Column("Keys")] - public string KeysString - { - get => KeyCombination.ToString(); - private set => KeyCombination = value; - } + public string KeysString { get; set; } [Column("Action")] - public int IntAction + public int IntAction { get; set; } + + [NotMapped] + public KeyCombination KeyCombination { - get => (int)Action; - set => Action = value; + get => KeysString; + set => KeysString = value.ToString(); + } + + [NotMapped] + public object Action + { + get => IntAction; + set => IntAction = (int)value; } } } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index edaf18a760..ab4854bfd2 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Input.Bindings [Resolved] private IKeyBindingStore store { get; set; } - public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(variant ?? 0); + public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(variant ?? 0); /// /// Create a new instance. diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index b8c2fa201f..8ccdb9249e 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Input.Bindings handler = game; } - public override IEnumerable DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings).Concat(AudioControlKeyBindings).Concat(EditorKeyBindings); + public override IEnumerable DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings).Concat(AudioControlKeyBindings).Concat(EditorKeyBindings); public IEnumerable GlobalKeyBindings => new[] { diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs index eb04766d04..088a314fec 100644 --- a/osu.Game/Input/Bindings/RealmKeyBinding.cs +++ b/osu.Game/Input/Bindings/RealmKeyBinding.cs @@ -8,14 +8,27 @@ using Realms; namespace osu.Game.Input.Bindings { [MapTo("KeyBinding")] - public class RealmKeyBinding : RealmObject, IHasGuidPrimaryKey + public class RealmKeyBinding : RealmObject, IHasGuidPrimaryKey, IKeyBinding { + [PrimaryKey] public string ID { get; set; } public int? RulesetID { get; set; } public int? Variant { get; set; } + KeyCombination IKeyBinding.KeyCombination + { + get => KeyCombination; + set => KeyCombination = value.ToString(); + } + + object IKeyBinding.Action + { + get => Action; + set => Action = (int)value; + } + public int Action { get; set; } public string KeyCombination { get; set; } diff --git a/osu.Game/Input/IKeyBindingStore.cs b/osu.Game/Input/IKeyBindingStore.cs index c5e68dc6ca..50994cb542 100644 --- a/osu.Game/Input/IKeyBindingStore.cs +++ b/osu.Game/Input/IKeyBindingStore.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Input.Bindings; +using osu.Game.Database; using osu.Game.Input.Bindings; namespace osu.Game.Input @@ -27,14 +28,14 @@ namespace osu.Game.Input /// The ruleset's internal ID. /// An optional variant. /// - List Query(int? rulesetId = null, int? variant = null); + List Query(int? rulesetId = null, int? variant = null); /// - /// Retrieve s for the specified action. + /// Retrieve s for the specified action. /// /// The action to lookup. - List Query(T action) where T : Enum; + List Query(T action) where T : Enum; - public void Update(KeyBinding buttonKeyBinding) => throw new NotImplementedException(); + void Update(IHasGuidPrimaryKey keyBinding, Action modification); } } diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 53eb0024f8..ad6bcb4c7c 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -49,16 +49,21 @@ namespace osu.Game.Input } } - public List Query(int? rulesetId = null, int? variant = null) - => query(rulesetId, variant).OfType().ToList(); + public List Query(int? rulesetId = null, int? variant = null) + => query(rulesetId, variant).OfType().ToList(); - public List Query(T action) where T : Enum + public List Query(T action) where T : Enum { int lookup = (int)(object)action; - return query(null, null).Where(rkb => (int)rkb.Action == lookup).OfType().ToList(); + return query(null, null).Where(rkb => (int)rkb.Action == lookup).OfType().ToList(); } - private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) + public void Update(IHasGuidPrimaryKey keyBinding, Action modification) + { + throw new NotImplementedException(); + } + + private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) { using (var usage = ContextFactory.GetForWrite()) { @@ -98,11 +103,11 @@ namespace osu.Game.Input private List query(int? rulesetId = null, int? variant = null) => ContextFactory.Get().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); - public void Update(KeyBinding keyBinding) + public void Update(DatabasedKeyBinding keyBinding) { using (ContextFactory.GetForWrite()) { - var dbKeyBinding = (DatabasedKeyBinding)keyBinding; + var dbKeyBinding = keyBinding; Refresh(ref dbKeyBinding); if (dbKeyBinding.KeyCombination.Equals(keyBinding.KeyCombination)) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 37d0ce18ed..2455578ddb 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -37,9 +37,9 @@ namespace osu.Game.Input /// A set of display strings for all the user's key configuration for the action. public IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction) { - foreach (var action in query().Where(b => (GlobalAction)b.KeyBinding.Action == globalAction)) + foreach (var action in query().Where(b => (GlobalAction)b.Action == globalAction)) { - string str = action.KeyBinding.KeyCombination.ReadableString(); + string str = ((IKeyBinding)action).KeyCombination.ReadableString(); // even if found, the readable string may be empty for an unbound action. if (str.Length > 0) @@ -49,14 +49,14 @@ namespace osu.Game.Input public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings); - private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) + private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) { using (var usage = ContextFactory.GetForWrite()) { // compare counts in database vs defaults foreach (var group in defaults.GroupBy(k => k.Action)) { - int count = query(rulesetId, variant).Count(k => (int)k.KeyBinding.Action == (int)group.Key); + int count = query(rulesetId, variant).Count(k => k.Action == (int)group.Key); int aimCount = group.Count(); if (aimCount <= count) @@ -87,32 +87,26 @@ namespace osu.Game.Input /// The ruleset's internal ID. /// An optional variant. /// - private List query(int? rulesetId = null, int? variant = null) => - ContextFactory.Get().All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); + private IQueryable query(int? rulesetId = null, int? variant = null) => + ContextFactory.Get().All().Where(b => b.RulesetID == rulesetId && b.Variant == variant); - public List Query(int? rulesetId = null, int? variant = null) - => query(rulesetId, variant).Select(k => k.KeyBinding).ToList(); + public List Query(int? rulesetId = null, int? variant = null) + => query(rulesetId, variant).ToList().Select(r => r.Detach()).ToList(); - public List Query(T action) + public List Query(T action) where T : Enum { int lookup = (int)(object)action; - return query(null, null).Where(rkb => rkb.Action == lookup).Select(k => k.KeyBinding).ToList(); + return query(null, null).Where(rkb => rkb.Action == lookup).ToList().Select(r => r.Detach()).ToList(); } - public void Update(KeyBinding keyBinding) + public void Update(IHasGuidPrimaryKey keyBinding, Action modification) { - using (ContextFactory.GetForWrite()) + using (var realm = ContextFactory.GetForWrite()) { - //todo: fix - // var dbKeyBinding = (RealmKeyBinding)keyBinding; - // Refresh(ref dbKeyBinding); - // - // if (dbKeyBinding.KeyCombination.Equals(keyBinding.KeyCombination)) - // return; - // - // dbKeyBinding.KeyCombination = keyBinding.KeyCombination; + var realmKeyBinding = realm.Context.Find(keyBinding.ID); + modification(realmKeyBinding); } KeyBindingChanged?.Invoke(); diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index d9f63328d0..87d51e5268 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -25,7 +26,7 @@ namespace osu.Game.Overlays.KeyBinding public class KeyBindingRow : Container, IFilterable { private readonly object action; - private readonly IEnumerable bindings; + private readonly IEnumerable bindings; private const float transition_time = 150; @@ -53,7 +54,7 @@ namespace osu.Game.Overlays.KeyBinding public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend((string)text.Text); - public KeyBindingRow(object action, IEnumerable bindings) + public KeyBindingRow(object action, IEnumerable bindings) { this.action = action; this.bindings = bindings; @@ -126,7 +127,7 @@ namespace osu.Game.Overlays.KeyBinding { var button = buttons[i++]; button.UpdateKeyCombination(d); - store.Update(button.KeyBinding); + store.Update((IHasGuidPrimaryKey)button.KeyBinding, k => k.KeyCombination = button.KeyBinding.KeyCombination); } } @@ -285,7 +286,7 @@ namespace osu.Game.Overlays.KeyBinding { if (bindTarget != null) { - store.Update(bindTarget.KeyBinding); + store.Update((IHasGuidPrimaryKey)bindTarget.KeyBinding, k => k.KeyCombination = bindTarget.KeyBinding.KeyCombination); bindTarget.IsBinding = false; Schedule(() => @@ -359,7 +360,7 @@ namespace osu.Game.Overlays.KeyBinding public class KeyButton : Container { - public readonly Framework.Input.Bindings.KeyBinding KeyBinding; + public readonly IKeyBinding KeyBinding; private readonly Box box; public readonly OsuSpriteText Text; @@ -381,7 +382,7 @@ namespace osu.Game.Overlays.KeyBinding } } - public KeyButton(Framework.Input.Bindings.KeyBinding keyBinding) + public KeyButton(IKeyBinding keyBinding) { KeyBinding = keyBinding; From 86daf65630592944e9f505c4db74745d9b8fa651 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jan 2021 15:49:11 +0900 Subject: [PATCH 097/520] Fix primary key not being populated for KeyBinding --- osu.Game/Database/IHasGuidPrimaryKey.cs | 1 + osu.Game/Database/RealmContextFactory.cs | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/IHasGuidPrimaryKey.cs b/osu.Game/Database/IHasGuidPrimaryKey.cs index 33618e990d..3b0888c654 100644 --- a/osu.Game/Database/IHasGuidPrimaryKey.cs +++ b/osu.Game/Database/IHasGuidPrimaryKey.cs @@ -20,6 +20,7 @@ namespace osu.Game.Database [JsonIgnore] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [PrimaryKey] string ID { get; set; } } } diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 2f6ccb8911..62e2dbba9a 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -163,7 +163,7 @@ namespace osu.Game.Database contexts.Value++; return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) { - SchemaVersion = 3, + SchemaVersion = 5, MigrationCallback = onMigration }); } @@ -211,6 +211,12 @@ namespace osu.Game.Database public RealmWrapper WrapChild(Func lookup) where TChild : RealmObject, IHasGuidPrimaryKey => new RealmWrapper(lookup(Get()), ContextFactory); + public void PerformUpdate(Action perform) + { + using (ContextFactory.GetForWrite()) + perform(this); + } + // ReSharper disable once CA2225 public static implicit operator T(RealmWrapper wrapper) => wrapper?.Get().Detach(); @@ -241,6 +247,7 @@ namespace osu.Game.Database .MaxDepth(2); c.CreateMap(); + c.CreateMap(); c.CreateMap(); c.CreateMap(); c.CreateMap(); From a1cb6d8c546327921b740aeb1ecc28b610177613 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jan 2021 17:44:12 +0900 Subject: [PATCH 098/520] Remove unnecesssary local conversion method --- osu.Game/Input/Bindings/RealmKeyBinding.cs | 11 ----------- osu.Game/Input/RealmKeyBindingStore.cs | 7 ++----- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs index 088a314fec..1e690ddbab 100644 --- a/osu.Game/Input/Bindings/RealmKeyBinding.cs +++ b/osu.Game/Input/Bindings/RealmKeyBinding.cs @@ -32,16 +32,5 @@ namespace osu.Game.Input.Bindings public int Action { get; set; } public string KeyCombination { get; set; } - - [Ignored] - public KeyBinding KeyBinding - { - get => new KeyBinding(KeyCombination, Action); - set - { - KeyCombination = value.KeyCombination.ToString(); - Action = (int)value.Action; - } - } } } diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 2455578ddb..33172921cb 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -68,11 +68,8 @@ namespace osu.Game.Input usage.Context.Add(new RealmKeyBinding { ID = Guid.NewGuid().ToString(), - KeyBinding = new KeyBinding - { - KeyCombination = insertable.KeyCombination, - Action = insertable.Action, - }, + KeyCombination = insertable.KeyCombination.ToString(), + Action = (int)insertable.Action, RulesetID = rulesetId, Variant = variant }); From 1abed11fb7b80977f6af07d2525c97d23ea917c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jan 2021 17:44:19 +0900 Subject: [PATCH 099/520] Add basic migration logic of key bindings to realm --- osu.Game/OsuGameBase.cs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 95e1bc69b3..3fa55ab594 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -24,6 +24,7 @@ using osu.Game.Online.API; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Database; @@ -265,10 +266,10 @@ namespace osu.Game dependencies.Cache(scorePerformanceManager); AddInternal(scorePerformanceManager); - // todo: migrate to realm - // dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); + migrateDataToRealm(); dependencies.CacheAs(KeyBindingStore = new RealmKeyBindingStore(realmFactory, RulesetStore)); + dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); dependencies.Cache(new SessionStatics()); @@ -322,6 +323,29 @@ namespace osu.Game Ruleset.BindValueChanged(onRulesetChanged); } + private void migrateDataToRealm() + { + using (var db = contextFactory.GetForWrite()) + using (var realm = realmFactory.GetForWrite()) + { + var existingBindings = db.Context.DatabasedKeyBinding; + + foreach (var dkb in existingBindings) + { + realm.Context.Add(new RealmKeyBinding + { + ID = Guid.NewGuid().ToString(), + KeyCombination = dkb.KeyCombination.ToString(), + Action = (int)dkb.Action, + RulesetID = dkb.RulesetID, + Variant = dkb.Variant + }); + } + + db.Context.RemoveRange(existingBindings); + } + } + private void onRulesetChanged(ValueChangedEvent r) { var dict = new Dictionary>(); From 56d34432f92b486e24623172b1cc18feeccdc421 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 15:56:41 +0900 Subject: [PATCH 100/520] Move public members up --- osu.Game/Input/RealmKeyBindingStore.cs | 44 +++++++++++++------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 33172921cb..9910882cef 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -49,6 +49,28 @@ namespace osu.Game.Input public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings); + public List Query(int? rulesetId = null, int? variant = null) + => query(rulesetId, variant).ToList().Select(r => r.Detach()).ToList(); + + public List Query(T action) + where T : Enum + { + int lookup = (int)(object)action; + + return query(null, null).Where(rkb => rkb.Action == lookup).ToList().Select(r => r.Detach()).ToList(); + } + + public void Update(IHasGuidPrimaryKey keyBinding, Action modification) + { + using (var realm = ContextFactory.GetForWrite()) + { + var realmKeyBinding = realm.Context.Find(keyBinding.ID); + modification(realmKeyBinding); + } + + KeyBindingChanged?.Invoke(); + } + private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) { using (var usage = ContextFactory.GetForWrite()) @@ -86,27 +108,5 @@ namespace osu.Game.Input /// private IQueryable query(int? rulesetId = null, int? variant = null) => ContextFactory.Get().All().Where(b => b.RulesetID == rulesetId && b.Variant == variant); - - public List Query(int? rulesetId = null, int? variant = null) - => query(rulesetId, variant).ToList().Select(r => r.Detach()).ToList(); - - public List Query(T action) - where T : Enum - { - int lookup = (int)(object)action; - - return query(null, null).Where(rkb => rkb.Action == lookup).ToList().Select(r => r.Detach()).ToList(); - } - - public void Update(IHasGuidPrimaryKey keyBinding, Action modification) - { - using (var realm = ContextFactory.GetForWrite()) - { - var realmKeyBinding = realm.Context.Find(keyBinding.ID); - modification(realmKeyBinding); - } - - KeyBindingChanged?.Invoke(); - } } } From f9717e8b693346c1867e0f0bbc9c69bca1b8f9f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 16:00:11 +0900 Subject: [PATCH 101/520] Don't migrate existing key bindings across if realm is already populated --- osu.Game/OsuGameBase.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3fa55ab594..4b64bf2e24 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -24,7 +24,6 @@ using osu.Game.Online.API; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; -using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Database; @@ -330,16 +329,20 @@ namespace osu.Game { var existingBindings = db.Context.DatabasedKeyBinding; - foreach (var dkb in existingBindings) + // only migrate data if the realm database is empty. + if (!realm.Context.All().Any()) { - realm.Context.Add(new RealmKeyBinding + foreach (var dkb in existingBindings) { - ID = Guid.NewGuid().ToString(), - KeyCombination = dkb.KeyCombination.ToString(), - Action = (int)dkb.Action, - RulesetID = dkb.RulesetID, - Variant = dkb.Variant - }); + realm.Context.Add(new RealmKeyBinding + { + ID = Guid.NewGuid().ToString(), + KeyCombination = dkb.KeyCombination.ToString(), + Action = (int)dkb.Action, + RulesetID = dkb.RulesetID, + Variant = dkb.Variant + }); + } } db.Context.RemoveRange(existingBindings); From 6fd098ca7cb84148d238a40a5c8c666cce0b327d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 16:18:25 +0900 Subject: [PATCH 102/520] Add full xmldoc to RealmKeyBindingStore --- osu.Game/Input/IKeyBindingStore.cs | 2 +- osu.Game/Input/KeyBindingStore.cs | 2 +- osu.Game/Input/RealmKeyBindingStore.cs | 42 +++++++++++++++++++++----- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/osu.Game/Input/IKeyBindingStore.cs b/osu.Game/Input/IKeyBindingStore.cs index 50994cb542..ef1d043c33 100644 --- a/osu.Game/Input/IKeyBindingStore.cs +++ b/osu.Game/Input/IKeyBindingStore.cs @@ -13,7 +13,7 @@ namespace osu.Game.Input { event Action KeyBindingChanged; - void Register(KeyBindingContainer manager); + void Register(KeyBindingContainer container); /// /// Retrieve all user-defined key combinations (in a format that can be displayed) for a specific action. diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index ad6bcb4c7c..c55d62b7d6 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -30,7 +30,7 @@ namespace osu.Game.Input } } - public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings); + public void Register(KeyBindingContainer container) => insertDefaults(container.DefaultKeyBindings); /// /// Retrieve all user-defined key combinations (in a format that can be displayed) for a specific action. diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 9910882cef..cd0b85cd8d 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -14,11 +14,15 @@ namespace osu.Game.Input { public class RealmKeyBindingStore : RealmBackedStore, IKeyBindingStore { + /// + /// Fired whenever any key binding change occurs, across all rulesets and types. + /// public event Action KeyBindingChanged; public RealmKeyBindingStore(RealmContextFactory contextFactory, RulesetStore rulesets, Storage storage = null) : base(contextFactory, storage) { + // populate defaults from rulesets. using (ContextFactory.GetForWrite()) { foreach (RulesetInfo info in rulesets.AvailableRulesets) @@ -47,11 +51,27 @@ namespace osu.Game.Input } } - public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings); + /// + /// Register a new type of , adding default bindings from . + /// + /// The container to populate defaults from. + public void Register(KeyBindingContainer container) => insertDefaults(container.DefaultKeyBindings); + /// + /// Retrieve all key bindings for the provided specification. + /// + /// An optional ruleset ID. If null, global bindings are returned. + /// An optional ruleset variant. If null, the no-variant bindings are returned. + /// A list of all key bindings found for the query, detached from the database. public List Query(int? rulesetId = null, int? variant = null) => query(rulesetId, variant).ToList().Select(r => r.Detach()).ToList(); + /// + /// Retrieve all key bindings for the provided action type. + /// + /// The action to lookup. + /// The enum type of the action. + /// A list of all key bindings found for the query, detached from the database. public List Query(T action) where T : Enum { @@ -60,12 +80,21 @@ namespace osu.Game.Input return query(null, null).Where(rkb => rkb.Action == lookup).ToList().Select(r => r.Detach()).ToList(); } + /// + /// Update the database mapping for the provided key binding. + /// + /// The key binding to update. Can be detached from the database. + /// The modification to apply to the key binding. public void Update(IHasGuidPrimaryKey keyBinding, Action modification) { using (var realm = ContextFactory.GetForWrite()) { - var realmKeyBinding = realm.Context.Find(keyBinding.ID); - modification(realmKeyBinding); + RealmKeyBinding realmBinding = keyBinding as RealmKeyBinding; + + if (realmBinding?.IsManaged != true) + realmBinding = realm.Context.Find(keyBinding.ID); + + modification(realmBinding); } KeyBindingChanged?.Invoke(); @@ -101,11 +130,10 @@ namespace osu.Game.Input } /// - /// Retrieve s for a specified ruleset/variant content. + /// Retrieve live queryable s for a specified ruleset/variant content. /// - /// The ruleset's internal ID. - /// An optional variant. - /// + /// An optional ruleset ID. If null, global bindings are returned. + /// An optional ruleset variant. If null, the no-variant bindings are returned. private IQueryable query(int? rulesetId = null, int? variant = null) => ContextFactory.Get().All().Where(b => b.RulesetID == rulesetId && b.Variant == variant); } From 6c90f9ceeda882f79526f4760f169a9e61905783 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 16:22:52 +0900 Subject: [PATCH 103/520] Move RealmWrapper to own file --- osu.Game/Database/RealmContextFactory.cs | 52 -------------------- osu.Game/Database/RealmWrapper.cs | 61 ++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 52 deletions(-) create mode 100644 osu.Game/Database/RealmWrapper.cs diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 62e2dbba9a..0cdcdc2ef0 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics.CodeAnalysis; using System.Threading; using AutoMapper; using osu.Framework.Platform; @@ -178,57 +177,6 @@ namespace osu.Game.Database } } - [SuppressMessage("ReSharper", "CA2225")] - public class RealmWrapper : IEquatable> - where T : RealmObject, IHasGuidPrimaryKey - { - public Guid ID { get; } - - private readonly ThreadLocal threadValues; - - public readonly IRealmFactory ContextFactory; - - public RealmWrapper(T original, IRealmFactory contextFactory) - { - ContextFactory = contextFactory; - ID = original.Guid; - - var originalContext = original.Realm; - - threadValues = new ThreadLocal(() => - { - var context = ContextFactory?.Get(); - - if (context == null || originalContext?.IsSameInstance(context) != false) - return original; - - return context.Find(ID); - }); - } - - public T Get() => threadValues.Value; - - public RealmWrapper WrapChild(Func lookup) - where TChild : RealmObject, IHasGuidPrimaryKey => new RealmWrapper(lookup(Get()), ContextFactory); - - public void PerformUpdate(Action perform) - { - using (ContextFactory.GetForWrite()) - perform(this); - } - - // ReSharper disable once CA2225 - public static implicit operator T(RealmWrapper wrapper) - => wrapper?.Get().Detach(); - - // ReSharper disable once CA2225 - public static implicit operator RealmWrapper(T obj) => obj.WrapAsUnmanaged(); - - public bool Equals(RealmWrapper other) => other != null && other.ID == ID; - - public override string ToString() => Get().ToString(); - } - public static class RealmExtensions { private static readonly IMapper mapper = new MapperConfiguration(c => diff --git a/osu.Game/Database/RealmWrapper.cs b/osu.Game/Database/RealmWrapper.cs new file mode 100644 index 0000000000..06b1bbee90 --- /dev/null +++ b/osu.Game/Database/RealmWrapper.cs @@ -0,0 +1,61 @@ +// 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.CodeAnalysis; +using System.Threading; +using Realms; + +namespace osu.Game.Database +{ + [SuppressMessage("ReSharper", "CA2225")] + public class RealmWrapper : IEquatable> + where T : RealmObject, IHasGuidPrimaryKey + { + public Guid ID { get; } + + private readonly ThreadLocal threadValues; + + public readonly IRealmFactory ContextFactory; + + public RealmWrapper(T original, IRealmFactory contextFactory) + { + ContextFactory = contextFactory; + ID = original.Guid; + + var originalContext = original.Realm; + + threadValues = new ThreadLocal(() => + { + var context = ContextFactory?.Get(); + + if (context == null || originalContext?.IsSameInstance(context) != false) + return original; + + return context.Find(ID); + }); + } + + public T Get() => threadValues.Value; + + public RealmWrapper WrapChild(Func lookup) + where TChild : RealmObject, IHasGuidPrimaryKey => new RealmWrapper(lookup(Get()), ContextFactory); + + public void PerformUpdate(Action perform) + { + using (ContextFactory.GetForWrite()) + perform(this); + } + + // ReSharper disable once CA2225 + public static implicit operator T(RealmWrapper wrapper) + => wrapper?.Get().Detach(); + + // ReSharper disable once CA2225 + public static implicit operator RealmWrapper(T obj) => obj.WrapAsUnmanaged(); + + public bool Equals(RealmWrapper other) => other != null && other.ID == ID; + + public override string ToString() => Get().ToString(); + } +} From cdb3d20fc62465946a98730452baf6a381da14e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 16:24:12 +0900 Subject: [PATCH 104/520] Remove unnecessary warning suppression --- osu.Game/Database/RealmWrapper.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Database/RealmWrapper.cs b/osu.Game/Database/RealmWrapper.cs index 06b1bbee90..9792cce527 100644 --- a/osu.Game/Database/RealmWrapper.cs +++ b/osu.Game/Database/RealmWrapper.cs @@ -2,13 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics.CodeAnalysis; using System.Threading; using Realms; namespace osu.Game.Database { - [SuppressMessage("ReSharper", "CA2225")] public class RealmWrapper : IEquatable> where T : RealmObject, IHasGuidPrimaryKey { From 5bb4d359826f2df850a167c502bda25408edd1f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 16:26:45 +0900 Subject: [PATCH 105/520] Make RealmWrapper nullable enabled --- osu.Game/Database/RealmWrapper.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmWrapper.cs b/osu.Game/Database/RealmWrapper.cs index 9792cce527..a754e208bd 100644 --- a/osu.Game/Database/RealmWrapper.cs +++ b/osu.Game/Database/RealmWrapper.cs @@ -5,6 +5,8 @@ using System; using System.Threading; using Realms; +#nullable enable + namespace osu.Game.Database { public class RealmWrapper : IEquatable> @@ -25,7 +27,7 @@ namespace osu.Game.Database threadValues = new ThreadLocal(() => { - var context = ContextFactory?.Get(); + var context = ContextFactory.Get(); if (context == null || originalContext?.IsSameInstance(context) != false) return original; @@ -42,17 +44,15 @@ namespace osu.Game.Database public void PerformUpdate(Action perform) { using (ContextFactory.GetForWrite()) - perform(this); + perform(Get()); } - // ReSharper disable once CA2225 - public static implicit operator T(RealmWrapper wrapper) + public static implicit operator T?(RealmWrapper? wrapper) => wrapper?.Get().Detach(); - // ReSharper disable once CA2225 public static implicit operator RealmWrapper(T obj) => obj.WrapAsUnmanaged(); - public bool Equals(RealmWrapper other) => other != null && other.ID == ID; + public bool Equals(RealmWrapper? other) => other != null && other.ID == ID; public override string ToString() => Get().ToString(); } From 9f64f6059fd2597bf55f595c4b7b2b44180327a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 16:30:55 +0900 Subject: [PATCH 106/520] Rename RealmWrapper to Live --- osu.Game/Database/{RealmWrapper.cs => Live.cs} | 14 +++++++------- osu.Game/Database/RealmContextFactory.cs | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) rename osu.Game/Database/{RealmWrapper.cs => Live.cs} (71%) diff --git a/osu.Game/Database/RealmWrapper.cs b/osu.Game/Database/Live.cs similarity index 71% rename from osu.Game/Database/RealmWrapper.cs rename to osu.Game/Database/Live.cs index a754e208bd..3c5e1db157 100644 --- a/osu.Game/Database/RealmWrapper.cs +++ b/osu.Game/Database/Live.cs @@ -9,7 +9,7 @@ using Realms; namespace osu.Game.Database { - public class RealmWrapper : IEquatable> + public class Live : IEquatable> where T : RealmObject, IHasGuidPrimaryKey { public Guid ID { get; } @@ -18,7 +18,7 @@ namespace osu.Game.Database public readonly IRealmFactory ContextFactory; - public RealmWrapper(T original, IRealmFactory contextFactory) + public Live(T original, IRealmFactory contextFactory) { ContextFactory = contextFactory; ID = original.Guid; @@ -38,8 +38,8 @@ namespace osu.Game.Database public T Get() => threadValues.Value; - public RealmWrapper WrapChild(Func lookup) - where TChild : RealmObject, IHasGuidPrimaryKey => new RealmWrapper(lookup(Get()), ContextFactory); + public Live WrapChild(Func lookup) + where TChild : RealmObject, IHasGuidPrimaryKey => new Live(lookup(Get()), ContextFactory); public void PerformUpdate(Action perform) { @@ -47,12 +47,12 @@ namespace osu.Game.Database perform(Get()); } - public static implicit operator T?(RealmWrapper? wrapper) + public static implicit operator T?(Live? wrapper) => wrapper?.Get().Detach(); - public static implicit operator RealmWrapper(T obj) => obj.WrapAsUnmanaged(); + public static implicit operator Live(T obj) => obj.WrapAsUnmanaged(); - public bool Equals(RealmWrapper? other) => other != null && other.ID == ID; + public bool Equals(Live? other) => other != null && other.ID == ID; public override string ToString() => Get().ToString(); } diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 0cdcdc2ef0..71e9f8c4e1 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -215,10 +215,10 @@ namespace osu.Game.Database return detached; } - public static RealmWrapper Wrap(this T obj, IRealmFactory contextFactory) - where T : RealmObject, IHasGuidPrimaryKey => new RealmWrapper(obj, contextFactory); + public static Live Wrap(this T obj, IRealmFactory contextFactory) + where T : RealmObject, IHasGuidPrimaryKey => new Live(obj, contextFactory); - public static RealmWrapper WrapAsUnmanaged(this T obj) - where T : RealmObject, IHasGuidPrimaryKey => new RealmWrapper(obj, null); + public static Live WrapAsUnmanaged(this T obj) + where T : RealmObject, IHasGuidPrimaryKey => new Live(obj, null); } } From 20584c9e163cdb9680fd7ec985a3826bb52f6dc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 16:50:14 +0900 Subject: [PATCH 107/520] Add full xmldoc for Live class --- osu.Game/Database/Live.cs | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/Live.cs b/osu.Game/Database/Live.cs index 3c5e1db157..86cecd51d8 100644 --- a/osu.Game/Database/Live.cs +++ b/osu.Game/Database/Live.cs @@ -9,25 +9,36 @@ using Realms; namespace osu.Game.Database { + /// + /// Provides a method of passing realm live objects across threads in a safe fashion. + /// + /// + /// To consume this as a live instance, the live object should be stored and accessed via each time. + /// To consume this as a detached instance, assign to a variable of type . The implicit conversion will handle detaching an instance. + /// + /// The underlying object type. Should be a with a primary key provided via . public class Live : IEquatable> where T : RealmObject, IHasGuidPrimaryKey { + /// + /// The primary key of the object. + /// public Guid ID { get; } private readonly ThreadLocal threadValues; - public readonly IRealmFactory ContextFactory; + private readonly IRealmFactory contextFactory; public Live(T original, IRealmFactory contextFactory) { - ContextFactory = contextFactory; + this.contextFactory = contextFactory; ID = original.Guid; var originalContext = original.Realm; threadValues = new ThreadLocal(() => { - var context = ContextFactory.Get(); + var context = this.contextFactory.Get(); if (context == null || originalContext?.IsSameInstance(context) != false) return original; @@ -36,14 +47,27 @@ namespace osu.Game.Database }); } + /// + /// Retrieve a live reference to the data. + /// public T Get() => threadValues.Value; + /// + /// Wrap a property of this instance as its own live access object. + /// + /// The child to return. + /// The underlying child object type. Should be a with a primary key provided via . + /// A wrapped instance of the child. public Live WrapChild(Func lookup) - where TChild : RealmObject, IHasGuidPrimaryKey => new Live(lookup(Get()), ContextFactory); + where TChild : RealmObject, IHasGuidPrimaryKey => new Live(lookup(Get()), contextFactory); + /// + /// Perform a write operation on this live object. + /// + /// The action to perform. public void PerformUpdate(Action perform) { - using (ContextFactory.GetForWrite()) + using (contextFactory.GetForWrite()) perform(Get()); } From 05ca016deb04f6e5166f5255d490d82831c7b11e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 16:57:40 +0900 Subject: [PATCH 108/520] Make Live implement IHasGuidPrimaryKey --- osu.Game/Database/Live.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/Live.cs b/osu.Game/Database/Live.cs index 86cecd51d8..7d607a4637 100644 --- a/osu.Game/Database/Live.cs +++ b/osu.Game/Database/Live.cs @@ -17,13 +17,19 @@ namespace osu.Game.Database /// To consume this as a detached instance, assign to a variable of type . The implicit conversion will handle detaching an instance. /// /// The underlying object type. Should be a with a primary key provided via . - public class Live : IEquatable> + public class Live : IEquatable>, IHasGuidPrimaryKey where T : RealmObject, IHasGuidPrimaryKey { /// /// The primary key of the object. /// - public Guid ID { get; } + public Guid Guid { get; } + + public string ID + { + get => Guid.ToString(); + set => throw new NotImplementedException(); + } private readonly ThreadLocal threadValues; @@ -32,7 +38,7 @@ namespace osu.Game.Database public Live(T original, IRealmFactory contextFactory) { this.contextFactory = contextFactory; - ID = original.Guid; + Guid = original.Guid; var originalContext = original.Realm; @@ -43,7 +49,7 @@ namespace osu.Game.Database if (context == null || originalContext?.IsSameInstance(context) != false) return original; - return context.Find(ID); + return context.Find(Guid); }); } @@ -76,7 +82,7 @@ namespace osu.Game.Database public static implicit operator Live(T obj) => obj.WrapAsUnmanaged(); - public bool Equals(Live? other) => other != null && other.ID == ID; + public bool Equals(Live? other) => other != null && other.Guid == Guid; public override string ToString() => Get().ToString(); } From 406e640fa9b6657c1a657ccb6f5425deb2b05015 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 16:59:56 +0900 Subject: [PATCH 109/520] Make key binding update method support all kinds of realm object states --- osu.Game/Input/RealmKeyBindingStore.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index cd0b85cd8d..5b2d2c0dc1 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -87,14 +87,22 @@ namespace osu.Game.Input /// The modification to apply to the key binding. public void Update(IHasGuidPrimaryKey keyBinding, Action modification) { + // the incoming instance could already be a live access object. + Live realmBinding = keyBinding as Live; + using (var realm = ContextFactory.GetForWrite()) { - RealmKeyBinding realmBinding = keyBinding as RealmKeyBinding; + if (realmBinding == null) + { + // the incoming instance could be a raw realm object. + if (!(keyBinding is RealmKeyBinding rkb)) + // if neither of the above cases succeeded, retrieve a realm object for further processing. + rkb = realm.Context.Find(keyBinding.ID); - if (realmBinding?.IsManaged != true) - realmBinding = realm.Context.Find(keyBinding.ID); + realmBinding = new Live(rkb, ContextFactory); + } - modification(realmBinding); + realmBinding.PerformUpdate(modification); } KeyBindingChanged?.Invoke(); From 70689eee2bf1603c3f1755cf46cc29bbb6029788 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 17:27:21 +0900 Subject: [PATCH 110/520] Perform initial lookup if original is not managed --- osu.Game/Database/Live.cs | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game/Database/Live.cs b/osu.Game/Database/Live.cs index 7d607a4637..f4882ae325 100644 --- a/osu.Game/Database/Live.cs +++ b/osu.Game/Database/Live.cs @@ -14,7 +14,7 @@ namespace osu.Game.Database /// /// /// To consume this as a live instance, the live object should be stored and accessed via each time. - /// To consume this as a detached instance, assign to a variable of type . The implicit conversion will handle detaching an instance. + /// To consume this as a detached instance, assign to a variable of type . The implicit conversion will handle detaching an instance. /// /// The underlying object type. Should be a with a primary key provided via . public class Live : IEquatable>, IHasGuidPrimaryKey @@ -33,24 +33,34 @@ namespace osu.Game.Database private readonly ThreadLocal threadValues; + private readonly T original; + private readonly IRealmFactory contextFactory; - public Live(T original, IRealmFactory contextFactory) + public Live(T item, IRealmFactory contextFactory) { this.contextFactory = contextFactory; - Guid = original.Guid; - var originalContext = original.Realm; + original = item; + Guid = item.Guid; - threadValues = new ThreadLocal(() => - { - var context = this.contextFactory.Get(); + threadValues = new ThreadLocal(getThreadLocalValue); - if (context == null || originalContext?.IsSameInstance(context) != false) - return original; + // the instance passed in may not be in a managed state. + // for now let's immediately retrieve a managed object on the current thread. + // in the future we may want to delay this until the first access (only populating the Guid at construction time). + if (!item.IsManaged) + original = Get(); + } - return context.Find(Guid); - }); + private T getThreadLocalValue() + { + var context = contextFactory.Get(); + + // only use the original if no context is available or the source realm is the same. + if (context == null || original.Realm?.IsSameInstance(context) == true) return original; + + return context.Find(ID); } /// From a13b6abcff74b5bd9782d8caf7dbf733b7417a59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 18:56:36 +0900 Subject: [PATCH 111/520] Remove incorrect default specification from IRealmFactory interface --- osu.Game/Database/IRealmFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/IRealmFactory.cs b/osu.Game/Database/IRealmFactory.cs index d65bcaebbe..7b126e10ba 100644 --- a/osu.Game/Database/IRealmFactory.cs +++ b/osu.Game/Database/IRealmFactory.cs @@ -7,7 +7,7 @@ namespace osu.Game.Database { public interface IRealmFactory { - public Realm Get() => Realm.GetInstance(); + Realm Get(); /// /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). From 6736db327aa51bce4345e07afe9358eb46731251 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 18:57:56 +0900 Subject: [PATCH 112/520] Remove scheduler being passed in for now --- osu.Game/Database/RealmContextFactory.cs | 10 ++-------- osu.Game/OsuGameBase.cs | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 71e9f8c4e1..5e8bda65f8 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.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. -using System; using System.Threading; using AutoMapper; using osu.Framework.Platform; using osu.Framework.Statistics; -using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Input.Bindings; @@ -21,7 +19,6 @@ namespace osu.Game.Database public class RealmContextFactory : IRealmFactory { private readonly Storage storage; - private readonly Scheduler scheduler; private const string database_name = @"client"; @@ -37,10 +34,10 @@ namespace osu.Game.Database private Transaction currentWriteTransaction; - public RealmContextFactory(Storage storage, Scheduler scheduler) + public RealmContextFactory(Storage storage) { this.storage = storage; - this.scheduler = scheduler; + recreateThreadContexts(); using (CreateContext()) @@ -98,9 +95,6 @@ namespace osu.Game.Database return new RealmWriteUsage(context, usageCompleted) { IsTransactionLeader = currentWriteTransaction != null && currentWriteUsages == 1 }; } - // TODO: remove if not necessary. - public void Schedule(Action action) => scheduler.Add(action); - private Realm getContextForCurrentThread() { var context = threadContexts.Value; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4b64bf2e24..513f44ad5f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -169,7 +169,7 @@ namespace osu.Game dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage)); - dependencies.Cache(realmFactory = new RealmContextFactory(Storage, Scheduler)); + dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); dependencies.CacheAs(Storage); From dd50b5870ecb6ef1d09ac3871f4efb7dcd27bf74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 18:58:56 +0900 Subject: [PATCH 113/520] Move extensions methods into own class --- osu.Game/Database/RealmContextFactory.cs | 53 --------------------- osu.Game/Database/RealmExtensions.cs | 60 ++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 53 deletions(-) create mode 100644 osu.Game/Database/RealmExtensions.cs diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 5e8bda65f8..f78dd65fcc 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -2,16 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Threading; -using AutoMapper; using osu.Framework.Platform; using osu.Framework.Statistics; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Input.Bindings; -using osu.Game.IO; -using osu.Game.Rulesets; -using osu.Game.Scoring; -using osu.Game.Skinning; using Realms; namespace osu.Game.Database @@ -170,49 +162,4 @@ namespace osu.Game.Database } } } - - public static class RealmExtensions - { - private static readonly IMapper mapper = new MapperConfiguration(c => - { - c.ShouldMapField = fi => false; - c.ShouldMapProperty = pi => pi.SetMethod != null && pi.SetMethod.IsPublic; - - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - - c.CreateMap() - .ForMember(s => s.Beatmaps, d => d.MapFrom(s => s.Beatmaps)) - .ForMember(s => s.Files, d => d.MapFrom(s => s.Files)) - .MaxDepth(2); - - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - }).CreateMapper(); - - public static T Detach(this T obj) where T : RealmObject - { - if (!obj.IsManaged) - return obj; - - var detached = mapper.Map(obj); - - //typeof(RealmObject).GetField("_realm", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.SetValue(detached, null); - - return detached; - } - - public static Live Wrap(this T obj, IRealmFactory contextFactory) - where T : RealmObject, IHasGuidPrimaryKey => new Live(obj, contextFactory); - - public static Live WrapAsUnmanaged(this T obj) - where T : RealmObject, IHasGuidPrimaryKey => new Live(obj, null); - } } diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs new file mode 100644 index 0000000000..8823d75b89 --- /dev/null +++ b/osu.Game/Database/RealmExtensions.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using AutoMapper; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Input.Bindings; +using osu.Game.IO; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Skinning; +using Realms; + +namespace osu.Game.Database +{ + public static class RealmExtensions + { + private static readonly IMapper mapper = new MapperConfiguration(c => + { + c.ShouldMapField = fi => false; + c.ShouldMapProperty = pi => pi.SetMethod != null && pi.SetMethod.IsPublic; + + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + + c.CreateMap() + .ForMember(s => s.Beatmaps, d => d.MapFrom(s => s.Beatmaps)) + .ForMember(s => s.Files, d => d.MapFrom(s => s.Files)) + .MaxDepth(2); + + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + }).CreateMapper(); + + public static T Detach(this T obj) where T : RealmObject + { + if (!obj.IsManaged) + return obj; + + var detached = mapper.Map(obj); + + //typeof(RealmObject).GetField("_realm", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.SetValue(detached, null); + + return detached; + } + + public static Live Wrap(this T obj, IRealmFactory contextFactory) + where T : RealmObject, IHasGuidPrimaryKey => new Live(obj, contextFactory); + + public static Live WrapAsUnmanaged(this T obj) + where T : RealmObject, IHasGuidPrimaryKey => new Live(obj, null); + } +} From d810af82eca67d14f62ad3301312489c3c58b3fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 19:28:07 +0900 Subject: [PATCH 114/520] Expose Live.Detach() method for ease of use --- osu.Game/Database/Live.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/Live.cs b/osu.Game/Database/Live.cs index f4882ae325..24a2aa258b 100644 --- a/osu.Game/Database/Live.cs +++ b/osu.Game/Database/Live.cs @@ -68,6 +68,11 @@ namespace osu.Game.Database /// public T Get() => threadValues.Value; + /// + /// Retrieve a detached copy of the data. + /// + public T Detach() => Get().Detach(); + /// /// Wrap a property of this instance as its own live access object. /// @@ -88,7 +93,7 @@ namespace osu.Game.Database } public static implicit operator T?(Live? wrapper) - => wrapper?.Get().Detach(); + => wrapper?.Detach() ?? null; public static implicit operator Live(T obj) => obj.WrapAsUnmanaged(); From fc55d67c66899de1a238a95466ce069adc919231 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 19:46:51 +0900 Subject: [PATCH 115/520] Add helper method for detaching lists from realm --- osu.Game/Database/RealmExtensions.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs index 8823d75b89..99df125f86 100644 --- a/osu.Game/Database/RealmExtensions.cs +++ b/osu.Game/Database/RealmExtensions.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.Collections.Generic; using AutoMapper; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -39,6 +40,16 @@ namespace osu.Game.Database c.CreateMap(); }).CreateMapper(); + public static List Detach(this List items) where T : RealmObject + { + var list = new List(); + + foreach (var obj in items) + list.Add(obj.Detach()); + + return list; + } + public static T Detach(this T obj) where T : RealmObject { if (!obj.IsManaged) From 536e7229d0cb82504a39f0a18e120da91e0b0f12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 19:47:43 +0900 Subject: [PATCH 116/520] Remove unused EF class and unnecessary interface --- osu.Game.Tests/Visual/TestSceneOsuGame.cs | 2 +- .../Bindings/DatabasedKeyBindingContainer.cs | 2 +- osu.Game/Input/IKeyBindingStore.cs | 41 ------ osu.Game/Input/KeyBindingStore.cs | 122 ------------------ osu.Game/Input/RealmKeyBindingStore.cs | 9 +- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 2 +- .../KeyBinding/KeyBindingsSubsection.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 2 +- 9 files changed, 10 insertions(+), 174 deletions(-) delete mode 100644 osu.Game/Input/IKeyBindingStore.cs delete mode 100644 osu.Game/Input/KeyBindingStore.cs diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs index eddaf36f92..bcad8f2d3c 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual typeof(FileStore), typeof(ScoreManager), typeof(BeatmapManager), - typeof(IKeyBindingStore), + typeof(RealmKeyBindingStore), typeof(SettingsStore), typeof(RulesetConfigCache), typeof(OsuColour), diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index ab4854bfd2..62c09440d5 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Input.Bindings private readonly int? variant; [Resolved] - private IKeyBindingStore store { get; set; } + private RealmKeyBindingStore store { get; set; } public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(variant ?? 0); diff --git a/osu.Game/Input/IKeyBindingStore.cs b/osu.Game/Input/IKeyBindingStore.cs deleted file mode 100644 index ef1d043c33..0000000000 --- a/osu.Game/Input/IKeyBindingStore.cs +++ /dev/null @@ -1,41 +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 System.Collections.Generic; -using osu.Framework.Input.Bindings; -using osu.Game.Database; -using osu.Game.Input.Bindings; - -namespace osu.Game.Input -{ - public interface IKeyBindingStore - { - event Action KeyBindingChanged; - - void Register(KeyBindingContainer container); - - /// - /// Retrieve all user-defined key combinations (in a format that can be displayed) for a specific action. - /// - /// The action to lookup. - /// A set of display strings for all the user's key configuration for the action. - IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction); - - /// - /// Retrieve s for a specified ruleset/variant content. - /// - /// The ruleset's internal ID. - /// An optional variant. - /// - List Query(int? rulesetId = null, int? variant = null); - - /// - /// Retrieve s for the specified action. - /// - /// The action to lookup. - List Query(T action) where T : Enum; - - void Update(IHasGuidPrimaryKey keyBinding, Action modification); - } -} diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs deleted file mode 100644 index c55d62b7d6..0000000000 --- a/osu.Game/Input/KeyBindingStore.cs +++ /dev/null @@ -1,122 +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 System.Collections.Generic; -using System.Linq; -using osu.Framework.Input.Bindings; -using osu.Framework.Platform; -using osu.Game.Database; -using osu.Game.Input.Bindings; -using osu.Game.Rulesets; - -namespace osu.Game.Input -{ - public class KeyBindingStore : DatabaseBackedStore, IKeyBindingStore - { - public event Action KeyBindingChanged; - - public KeyBindingStore(DatabaseContextFactory contextFactory, RulesetStore rulesets, Storage storage = null) - : base(contextFactory, storage) - { - using (ContextFactory.GetForWrite()) - { - foreach (var info in rulesets.AvailableRulesets) - { - var ruleset = info.CreateInstance(); - foreach (var variant in ruleset.AvailableVariants) - insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant); - } - } - } - - public void Register(KeyBindingContainer container) => insertDefaults(container.DefaultKeyBindings); - - /// - /// Retrieve all user-defined key combinations (in a format that can be displayed) for a specific action. - /// - /// The action to lookup. - /// A set of display strings for all the user's key configuration for the action. - public IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction) - { - foreach (var action in query().Where(b => (GlobalAction)b.Action == globalAction)) - { - string str = action.KeyCombination.ReadableString(); - - // even if found, the readable string may be empty for an unbound action. - if (str.Length > 0) - yield return str; - } - } - - public List Query(int? rulesetId = null, int? variant = null) - => query(rulesetId, variant).OfType().ToList(); - - public List Query(T action) where T : Enum - { - int lookup = (int)(object)action; - return query(null, null).Where(rkb => (int)rkb.Action == lookup).OfType().ToList(); - } - - public void Update(IHasGuidPrimaryKey keyBinding, Action modification) - { - throw new NotImplementedException(); - } - - private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) - { - using (var usage = ContextFactory.GetForWrite()) - { - // compare counts in database vs defaults - foreach (var group in defaults.GroupBy(k => k.Action)) - { - int count = query(rulesetId, variant).Count(k => (int)k.Action == (int)group.Key); - int aimCount = group.Count(); - - if (aimCount <= count) - continue; - - foreach (var insertable in group.Skip(count).Take(aimCount - count)) - { - // insert any defaults which are missing. - usage.Context.DatabasedKeyBinding.Add(new DatabasedKeyBinding - { - KeyCombination = insertable.KeyCombination, - Action = insertable.Action, - RulesetID = rulesetId, - Variant = variant - }); - - // required to ensure stable insert order (https://github.com/dotnet/efcore/issues/11686) - usage.Context.SaveChanges(); - } - } - } - } - - /// - /// Retrieve s for a specified ruleset/variant content. - /// - /// The ruleset's internal ID. - /// An optional variant. - /// - private List query(int? rulesetId = null, int? variant = null) => - ContextFactory.Get().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); - - public void Update(DatabasedKeyBinding keyBinding) - { - using (ContextFactory.GetForWrite()) - { - var dbKeyBinding = keyBinding; - Refresh(ref dbKeyBinding); - - if (dbKeyBinding.KeyCombination.Equals(keyBinding.KeyCombination)) - return; - - dbKeyBinding.KeyCombination = keyBinding.KeyCombination; - } - - KeyBindingChanged?.Invoke(); - } - } -} diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 5b2d2c0dc1..fccd216e4d 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets; namespace osu.Game.Input { - public class RealmKeyBindingStore : RealmBackedStore, IKeyBindingStore + public class RealmKeyBindingStore : RealmBackedStore { /// /// Fired whenever any key binding change occurs, across all rulesets and types. @@ -63,8 +63,7 @@ namespace osu.Game.Input /// An optional ruleset ID. If null, global bindings are returned. /// An optional ruleset variant. If null, the no-variant bindings are returned. /// A list of all key bindings found for the query, detached from the database. - public List Query(int? rulesetId = null, int? variant = null) - => query(rulesetId, variant).ToList().Select(r => r.Detach()).ToList(); + public List Query(int? rulesetId = null, int? variant = null) => query(rulesetId, variant).ToList(); /// /// Retrieve all key bindings for the provided action type. @@ -72,12 +71,12 @@ namespace osu.Game.Input /// The action to lookup. /// The enum type of the action. /// A list of all key bindings found for the query, detached from the database. - public List Query(T action) + public List Query(T action) where T : Enum { int lookup = (int)(object)action; - return query(null, null).Where(rkb => rkb.Action == lookup).ToList().Select(r => r.Detach()).ToList(); + return query(null, null).Where(rkb => rkb.Action == lookup).ToList(); } /// diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 513f44ad5f..07918748df 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -73,7 +73,7 @@ namespace osu.Game protected FileStore FileStore; - protected IKeyBindingStore KeyBindingStore; + protected RealmKeyBindingStore KeyBindingStore; protected SettingsStore SettingsStore; diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 87d51e5268..0a065c9dbc 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.KeyBinding } [Resolved] - private IKeyBindingStore store { get; set; } + private RealmKeyBindingStore store { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index b5d6bc98c3..fbd9a17e2b 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.KeyBinding } [BackgroundDependencyLoader] - private void load(IKeyBindingStore store) + private void load(RealmKeyBindingStore store) { var bindings = store.Query(Ruleset?.ID, variant); diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 747f5e9bd0..69e4f734ad 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Toolbar protected FillFlowContainer Flow; [Resolved] - private IKeyBindingStore keyBindings { get; set; } + private RealmKeyBindingStore keyBindings { get; set; } protected ToolbarButton() : base(HoverSampleSet.Loud) From 8f9b19a76e861f871a653afda37713dc4a6a200c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jan 2021 19:47:51 +0900 Subject: [PATCH 117/520] Detach at point of usage, rather than point of retrieval --- osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs | 3 ++- osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 62c09440d5..48cab674ca 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Input.Bindings; +using osu.Game.Database; using osu.Game.Rulesets; namespace osu.Game.Input.Bindings @@ -63,7 +64,7 @@ namespace osu.Game.Input.Bindings // fallback to defaults instead. KeyBindings = DefaultKeyBindings; else - KeyBindings = store.Query(ruleset?.ID, variant); + KeyBindings = store.Query(ruleset?.ID, variant).Detach(); } } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index fbd9a17e2b..bdcbf02ee6 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -6,12 +6,13 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Game.Database; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Input; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osuTK; -using osu.Game.Graphics; namespace osu.Game.Overlays.KeyBinding { @@ -34,14 +35,14 @@ namespace osu.Game.Overlays.KeyBinding [BackgroundDependencyLoader] private void load(RealmKeyBindingStore store) { - var bindings = store.Query(Ruleset?.ID, variant); + var bindings = store.Query(Ruleset?.ID, variant).Detach(); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { int intKey = (int)defaultGroup.Key; // one row per valid action. - Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => ((int)b.Action).Equals(intKey))) + Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.Action.Equals(intKey))) { AllowMainMouseButtons = Ruleset != null, Defaults = defaultGroup.Select(d => d.KeyCombination) From 0789621b857b30ccc6aee8d5dc151eca2c367451 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jan 2021 13:51:37 +0900 Subject: [PATCH 118/520] Elaborate on comment mentioning migrations --- osu.Game/Database/RealmContextFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index f78dd65fcc..4325d58d07 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -34,11 +34,11 @@ namespace osu.Game.Database using (CreateContext()) { - // ensure our schema is up-to-date and migrated. + // creating a context will ensure our schema is up-to-date and migrated. } } - private void onMigration(Migration migration, ulong oldschemaversion) + private void onMigration(Migration migration, ulong lastSchemaVersion) { } From ffb42c37dfd644355e3532d5588258029d16fb63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jan 2021 14:25:07 +0900 Subject: [PATCH 119/520] Move schema version to const --- osu.Game/Database/RealmContextFactory.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 4325d58d07..243f8c2847 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -14,6 +14,8 @@ namespace osu.Game.Database private const string database_name = @"client"; + private const int schema_version = 5; + private ThreadLocal threadContexts; private readonly object writeLock = new object(); @@ -148,8 +150,8 @@ namespace osu.Game.Database contexts.Value++; return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) { - SchemaVersion = 5, - MigrationCallback = onMigration + SchemaVersion = schema_version, + MigrationCallback = onMigration, }); } From 8cbad1dc1c318db308affd0514a506aa160a148d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jan 2021 14:25:22 +0900 Subject: [PATCH 120/520] Add logging of opened and created contexts --- osu.Game/Database/RealmContextFactory.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 243f8c2847..e0fd44ed4a 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -48,7 +48,8 @@ namespace osu.Game.Database private static readonly GlobalStatistic writes = GlobalStatistics.Get("Realm", "Get (Write)"); private static readonly GlobalStatistic commits = GlobalStatistics.Get("Realm", "Commits"); private static readonly GlobalStatistic rollbacks = GlobalStatistics.Get("Realm", "Rollbacks"); - private static readonly GlobalStatistic contexts = GlobalStatistics.Get("Realm", "Contexts"); + private static readonly GlobalStatistic contexts_open = GlobalStatistics.Get("Realm", "Contexts (Open)"); + private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get("Realm", "Contexts (Created)"); /// /// Get a context for the current thread for read-only usage. @@ -92,11 +93,16 @@ namespace osu.Game.Database private Realm getContextForCurrentThread() { var context = threadContexts.Value; + if (context?.IsClosed != false) threadContexts.Value = context = CreateContext(); + contexts_open.Value = threadContexts.Values.Count; + if (!refreshCompleted.Value) { + // to keep things simple, realm refreshes are currently performed per thread context at the point of retrieval. + // in the future this should likely be run as part of the update loop for the main (update thread) context. context.Refresh(); refreshCompleted.Value = true; } @@ -147,7 +153,8 @@ namespace osu.Game.Database protected virtual Realm CreateContext() { - contexts.Value++; + contexts_created.Value++; + return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) { SchemaVersion = schema_version, From 0dca9c8c464eae0440c4426401d76e52897e8afa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jan 2021 14:36:35 +0900 Subject: [PATCH 121/520] Tidy up RealmContextFactory; remove delete/dispose method which wouldn't work due to threading --- osu.Game/Database/RealmContextFactory.cs | 52 +++++++++--------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index e0fd44ed4a..e11379869a 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Threading; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; using Realms; @@ -16,8 +17,11 @@ namespace osu.Game.Database private const int schema_version = 5; - private ThreadLocal threadContexts; + private readonly ThreadLocal threadContexts; + /// + /// Lock object which is held for the duration of a write operation (via ). + /// private readonly object writeLock = new object(); private ThreadLocal refreshCompleted = new ThreadLocal(); @@ -32,10 +36,11 @@ namespace osu.Game.Database { this.storage = storage; - recreateThreadContexts(); + threadContexts = new ThreadLocal(createContext, true); - using (CreateContext()) + using (var realm = Get()) { + Logger.Log($"Opened realm {database_name} at version {realm.Config.SchemaVersion}"); // creating a context will ensure our schema is up-to-date and migrated. } } @@ -95,7 +100,7 @@ namespace osu.Game.Database var context = threadContexts.Value; if (context?.IsClosed != false) - threadContexts.Value = context = CreateContext(); + threadContexts.Value = context = createContext(); contexts_open.Value = threadContexts.Values.Count; @@ -110,6 +115,17 @@ namespace osu.Game.Database return context; } + private Realm createContext() + { + contexts_created.Value++; + + return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) + { + SchemaVersion = schema_version, + MigrationCallback = onMigration, + }); + } + private void usageCompleted(RealmWriteUsage usage) { int usages = Interlocked.Decrement(ref currentWriteUsages); @@ -142,33 +158,5 @@ namespace osu.Game.Database Monitor.Exit(writeLock); } } - - private void recreateThreadContexts() - { - // Contexts for other threads are not disposed as they may be in use elsewhere. Instead, fresh contexts are exposed - // for other threads to use, and we rely on the finalizer inside OsuDbContext to handle their previous contexts - threadContexts?.Value.Dispose(); - threadContexts = new ThreadLocal(CreateContext, true); - } - - protected virtual Realm CreateContext() - { - contexts_created.Value++; - - return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) - { - SchemaVersion = schema_version, - MigrationCallback = onMigration, - }); - } - - public void ResetDatabase() - { - lock (writeLock) - { - recreateThreadContexts(); - storage.DeleteDatabase(database_name); - } - } } } From 2e4c3c8e3941a15eaa2d7a3703d2ebe1c9b3fdd6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jan 2021 14:42:43 +0900 Subject: [PATCH 122/520] Avoid closing initial context after migrations (unnecessary) --- osu.Game/Database/RealmContextFactory.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index e11379869a..fa0fecc90c 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -38,11 +38,9 @@ namespace osu.Game.Database threadContexts = new ThreadLocal(createContext, true); - using (var realm = Get()) - { - Logger.Log($"Opened realm {database_name} at version {realm.Config.SchemaVersion}"); - // creating a context will ensure our schema is up-to-date and migrated. - } + // creating a context will ensure our schema is up-to-date and migrated. + var realm = Get(); + Logger.Log($"Opened realm \"{realm.Config.DatabasePath}\" at version {realm.Config.SchemaVersion}"); } private void onMigration(Migration migration, ulong lastSchemaVersion) From ff16d2f490dc7836dd96728e4ffdb81b20db185c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jan 2021 14:55:45 +0900 Subject: [PATCH 123/520] Mark classes nullable --- osu.Game/Database/RealmBackedStore.cs | 6 ++++-- osu.Game/Input/RealmKeyBindingStore.cs | 23 ++++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game/Database/RealmBackedStore.cs b/osu.Game/Database/RealmBackedStore.cs index e37831d9d5..4e58ef773b 100644 --- a/osu.Game/Database/RealmBackedStore.cs +++ b/osu.Game/Database/RealmBackedStore.cs @@ -3,15 +3,17 @@ using osu.Framework.Platform; +#nullable enable + namespace osu.Game.Database { public abstract class RealmBackedStore { - protected readonly Storage Storage; + protected readonly Storage? Storage; protected readonly IRealmFactory ContextFactory; - protected RealmBackedStore(IRealmFactory contextFactory, Storage storage = null) + protected RealmBackedStore(IRealmFactory contextFactory, Storage? storage = null) { ContextFactory = contextFactory; Storage = storage; diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index fccd216e4d..95751306f3 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -10,6 +10,8 @@ using osu.Game.Database; using osu.Game.Input.Bindings; using osu.Game.Rulesets; +#nullable enable + namespace osu.Game.Input { public class RealmKeyBindingStore : RealmBackedStore @@ -17,19 +19,22 @@ namespace osu.Game.Input /// /// Fired whenever any key binding change occurs, across all rulesets and types. /// - public event Action KeyBindingChanged; + public event Action? KeyBindingChanged; - public RealmKeyBindingStore(RealmContextFactory contextFactory, RulesetStore rulesets, Storage storage = null) + public RealmKeyBindingStore(RealmContextFactory contextFactory, RulesetStore? rulesets, Storage? storage = null) : base(contextFactory, storage) { - // populate defaults from rulesets. - using (ContextFactory.GetForWrite()) + if (rulesets != null) { - foreach (RulesetInfo info in rulesets.AvailableRulesets) + // populate defaults from rulesets. + using (ContextFactory.GetForWrite()) { - var ruleset = info.CreateInstance(); - foreach (var variant in ruleset.AvailableVariants) - insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant); + foreach (RulesetInfo info in rulesets.AvailableRulesets) + { + var ruleset = info.CreateInstance(); + foreach (var variant in ruleset.AvailableVariants) + insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant); + } } } } @@ -87,7 +92,7 @@ namespace osu.Game.Input public void Update(IHasGuidPrimaryKey keyBinding, Action modification) { // the incoming instance could already be a live access object. - Live realmBinding = keyBinding as Live; + Live? realmBinding = keyBinding as Live; using (var realm = ContextFactory.GetForWrite()) { From a6997a6fc67d44754c59d6c71d112baddefe81a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jan 2021 14:59:48 +0900 Subject: [PATCH 124/520] Move ruleset key binding registration to an explicit method rather than the constructor --- osu.Game/Input/RealmKeyBindingStore.cs | 30 ++++++++++++++------------ osu.Game/OsuGameBase.cs | 6 +++++- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 95751306f3..8b962e2e9a 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -21,22 +21,9 @@ namespace osu.Game.Input /// public event Action? KeyBindingChanged; - public RealmKeyBindingStore(RealmContextFactory contextFactory, RulesetStore? rulesets, Storage? storage = null) + public RealmKeyBindingStore(RealmContextFactory contextFactory, Storage? storage = null) : base(contextFactory, storage) { - if (rulesets != null) - { - // populate defaults from rulesets. - using (ContextFactory.GetForWrite()) - { - foreach (RulesetInfo info in rulesets.AvailableRulesets) - { - var ruleset = info.CreateInstance(); - foreach (var variant in ruleset.AvailableVariants) - insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant); - } - } - } } /// @@ -62,6 +49,21 @@ namespace osu.Game.Input /// The container to populate defaults from. public void Register(KeyBindingContainer container) => insertDefaults(container.DefaultKeyBindings); + /// + /// Register a ruleset, adding default bindings for each of its variants. + /// + /// The ruleset to populate defaults from. + public void Register(RulesetInfo ruleset) + { + var instance = ruleset.CreateInstance(); + + using (ContextFactory.GetForWrite()) + { + foreach (var variant in instance.AvailableVariants) + insertDefaults(instance.GetDefaultKeyBindings(variant), ruleset.ID, variant); + } + } + /// /// Retrieve all key bindings for the provided specification. /// diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 07918748df..65eca8255e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -267,7 +267,7 @@ namespace osu.Game migrateDataToRealm(); - dependencies.CacheAs(KeyBindingStore = new RealmKeyBindingStore(realmFactory, RulesetStore)); + dependencies.CacheAs(KeyBindingStore = new RealmKeyBindingStore(realmFactory)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); @@ -310,6 +310,10 @@ namespace osu.Game base.Content.Add(CreateScalingContainer().WithChild(MenuCursorContainer)); KeyBindingStore.Register(globalBindings); + + foreach (var r in RulesetStore.AvailableRulesets) + KeyBindingStore.Register(r); + dependencies.Cache(globalBindings); PreviewTrackManager previewTrackManager; From 9a5410e5d235431cc8f2394cd1d1567f9a38a676 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jan 2021 15:19:50 +0900 Subject: [PATCH 125/520] Add basic test coverage --- .../Database/TestRealmKeyBindingStore.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 osu.Game.Tests/Database/TestRealmKeyBindingStore.cs diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs new file mode 100644 index 0000000000..d8eb3a9906 --- /dev/null +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -0,0 +1,91 @@ +// 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.IO; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Platform; +using osu.Game.Database; +using osu.Game.Input; +using osu.Game.Input.Bindings; +using osuTK.Input; + +namespace osu.Game.Tests.Database +{ + [TestFixture] + public class TestRealmKeyBindingStore + { + private NativeStorage storage; + + private RealmKeyBindingStore keyBindingStore; + + private RealmContextFactory realmContextFactory; + + [SetUp] + public void SetUp() + { + var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + + storage = new NativeStorage(directory.FullName); + + realmContextFactory = new RealmContextFactory(storage); + keyBindingStore = new RealmKeyBindingStore(realmContextFactory); + } + + [Test] + public void TestDefaultsPopulationAndQuery() + { + Assert.That(keyBindingStore.Query().Count, Is.EqualTo(0)); + + KeyBindingContainer testContainer = new TestKeyBindingContainer(); + + keyBindingStore.Register(testContainer); + + Assert.That(keyBindingStore.Query().Count, Is.EqualTo(3)); + + Assert.That(keyBindingStore.Query(GlobalAction.Back).Count, Is.EqualTo(1)); + Assert.That(keyBindingStore.Query(GlobalAction.Select).Count, Is.EqualTo(2)); + } + + [Test] + public void TestUpdateViaQueriedReference() + { + KeyBindingContainer testContainer = new TestKeyBindingContainer(); + + keyBindingStore.Register(testContainer); + + var backBinding = keyBindingStore.Query(GlobalAction.Back).Single(); + + Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape })); + + keyBindingStore.Update(backBinding, binding => binding.KeyCombination = new KeyCombination(InputKey.BackSpace)); + + Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); + + // check still correct after re-query. + backBinding = keyBindingStore.Query(GlobalAction.Back).Single(); + Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); + } + + [TearDown] + public void TearDown() + { + storage.DeleteDirectory(string.Empty); + } + + public class TestKeyBindingContainer : KeyBindingContainer + { + public override IEnumerable DefaultKeyBindings => + new[] + { + new KeyBinding(InputKey.Escape, GlobalAction.Back), + new KeyBinding(InputKey.Enter, GlobalAction.Select), + new KeyBinding(InputKey.Space, GlobalAction.Select), + }; + } + } +} From 7769d95e7b62a11047a053b7bdeee8cc84e1ecfa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jan 2021 15:48:26 +0900 Subject: [PATCH 126/520] Add xmldoc for extension methods --- osu.Game/Database/RealmExtensions.cs | 53 +++++++++++++++++++++------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs index 99df125f86..e7dd335ea3 100644 --- a/osu.Game/Database/RealmExtensions.cs +++ b/osu.Game/Database/RealmExtensions.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; using System.Collections.Generic; using AutoMapper; using osu.Game.Beatmaps; @@ -40,6 +41,12 @@ namespace osu.Game.Database c.CreateMap(); }).CreateMapper(); + /// + /// Create a detached copy of the each item in the list. + /// + /// A list of managed s to detach. + /// The type of object. + /// A list containing non-managed copies of provided items. public static List Detach(this List items) where T : RealmObject { var list = new List(); @@ -50,22 +57,44 @@ namespace osu.Game.Database return list; } - public static T Detach(this T obj) where T : RealmObject + /// + /// Create a detached copy of the each item in the list. + /// + /// The managed to detach. + /// The type of object. + /// A non-managed copy of provided item. Will return the provided item if already detached. + public static T Detach(this T item) where T : RealmObject { - if (!obj.IsManaged) - return obj; + if (!item.IsManaged) + return item; - var detached = mapper.Map(obj); - - //typeof(RealmObject).GetField("_realm", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.SetValue(detached, null); - - return detached; + return mapper.Map(item); } - public static Live Wrap(this T obj, IRealmFactory contextFactory) - where T : RealmObject, IHasGuidPrimaryKey => new Live(obj, contextFactory); + /// + /// Wrap a managed instance of a realm object in a . + /// + /// The item to wrap. + /// A factory to retrieve realm contexts from. + /// The type of object. + /// A wrapped instance of the provided item. + public static Live Wrap(this T item, IRealmFactory contextFactory) + where T : RealmObject, IHasGuidPrimaryKey => new Live(item, contextFactory); - public static Live WrapAsUnmanaged(this T obj) - where T : RealmObject, IHasGuidPrimaryKey => new Live(obj, null); + /// + /// Wrap an unmanaged instance of a realm object in a . + /// + /// The item to wrap. + /// The type of object. + /// A wrapped instance of the provided item. + /// Throws if the provided item is managed. + public static Live WrapAsUnmanaged(this T item) + where T : RealmObject, IHasGuidPrimaryKey + { + if (item.IsManaged) + throw new ArgumentException("Provided item must not be managed", nameof(item)); + + return new Live(item, null); + } } } From f0a9688baa5be4962c8908fd5adb46b8f1ec78d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jan 2021 15:50:09 +0900 Subject: [PATCH 127/520] Remove unnecessary mapped type --- osu.Game/Database/RealmExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs index e7dd335ea3..7b63395192 100644 --- a/osu.Game/Database/RealmExtensions.cs +++ b/osu.Game/Database/RealmExtensions.cs @@ -32,7 +32,6 @@ namespace osu.Game.Database .ForMember(s => s.Files, d => d.MapFrom(s => s.Files)) .MaxDepth(2); - c.CreateMap(); c.CreateMap(); c.CreateMap(); c.CreateMap(); From 46a1d99c742c58502da7a1ffbbee7eef94f3c2e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jan 2021 17:01:16 +0900 Subject: [PATCH 128/520] Allow detach to be run against an IQueryable directly --- osu.Game/Database/RealmExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs index 7b63395192..b25299bf23 100644 --- a/osu.Game/Database/RealmExtensions.cs +++ b/osu.Game/Database/RealmExtensions.cs @@ -41,12 +41,12 @@ namespace osu.Game.Database }).CreateMapper(); /// - /// Create a detached copy of the each item in the list. + /// Create a detached copy of the each item in the collection. /// /// A list of managed s to detach. /// The type of object. /// A list containing non-managed copies of provided items. - public static List Detach(this List items) where T : RealmObject + public static List Detach(this IEnumerable items) where T : RealmObject { var list = new List(); From 765d9cfae1c38e09fba32e3ab667f6f40651d63f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jan 2021 17:01:40 +0900 Subject: [PATCH 129/520] Use direct access for query pattern --- .../Database/TestRealmKeyBindingStore.cs | 2 -- .../Bindings/DatabasedKeyBindingContainer.cs | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index d8eb3a9906..3402b95739 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -6,13 +6,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; using NUnit.Framework; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Input; using osu.Game.Input.Bindings; -using osuTK.Input; namespace osu.Game.Tests.Database { diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 48cab674ca..03da76b330 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Database; @@ -24,6 +25,9 @@ namespace osu.Game.Input.Bindings [Resolved] private RealmKeyBindingStore store { get; set; } + [Resolved] + private RealmContextFactory realmFactory { get; set; } + public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(variant ?? 0); /// @@ -64,7 +68,16 @@ namespace osu.Game.Input.Bindings // fallback to defaults instead. KeyBindings = DefaultKeyBindings; else - KeyBindings = store.Query(ruleset?.ID, variant).Detach(); + { + var rulesetId = ruleset?.ID; + + // #1 + KeyBindings = store.Query(rulesetId, variant).Detach(); + + // #2 (Clearly shows lifetime of realm context access) + using (var realm = realmFactory.Get()) + KeyBindings = realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).Detach(); + } } } } From 192e58e0c6c447cb0ae93c641e1e19396c7a22a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jan 2021 16:53:04 +0900 Subject: [PATCH 130/520] Update all read queries to use direct realm subscriptions/queries --- .../Database/TestRealmKeyBindingStore.cs | 14 +++-- osu.Game/Database/RealmBackedStore.cs | 6 +- osu.Game/Database/RealmContextFactory.cs | 2 +- .../Bindings/DatabasedKeyBindingContainer.cs | 55 +++++++++++-------- osu.Game/Input/RealmKeyBindingStore.cs | 36 +++--------- .../KeyBinding/KeyBindingsSubsection.cs | 25 +++++---- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 52 ++++++++++-------- 7 files changed, 97 insertions(+), 93 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 3402b95739..58633e2f03 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -37,18 +37,20 @@ namespace osu.Game.Tests.Database [Test] public void TestDefaultsPopulationAndQuery() { - Assert.That(keyBindingStore.Query().Count, Is.EqualTo(0)); + Assert.That(query().Count, Is.EqualTo(0)); KeyBindingContainer testContainer = new TestKeyBindingContainer(); keyBindingStore.Register(testContainer); - Assert.That(keyBindingStore.Query().Count, Is.EqualTo(3)); + Assert.That(query().Count, Is.EqualTo(3)); - Assert.That(keyBindingStore.Query(GlobalAction.Back).Count, Is.EqualTo(1)); - Assert.That(keyBindingStore.Query(GlobalAction.Select).Count, Is.EqualTo(2)); + Assert.That(query().Where(k => k.Action == (int)GlobalAction.Back).Count, Is.EqualTo(1)); + Assert.That(query().Where(k => k.Action == (int)GlobalAction.Select).Count, Is.EqualTo(2)); } + private IQueryable query() => realmContextFactory.Get().All(); + [Test] public void TestUpdateViaQueriedReference() { @@ -56,7 +58,7 @@ namespace osu.Game.Tests.Database keyBindingStore.Register(testContainer); - var backBinding = keyBindingStore.Query(GlobalAction.Back).Single(); + var backBinding = query().Single(k => k.Action == (int)GlobalAction.Back); Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape })); @@ -65,7 +67,7 @@ namespace osu.Game.Tests.Database Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); // check still correct after re-query. - backBinding = keyBindingStore.Query(GlobalAction.Back).Single(); + backBinding = query().Single(k => k.Action == (int)GlobalAction.Back); Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); } diff --git a/osu.Game/Database/RealmBackedStore.cs b/osu.Game/Database/RealmBackedStore.cs index 4e58ef773b..bc67e332fe 100644 --- a/osu.Game/Database/RealmBackedStore.cs +++ b/osu.Game/Database/RealmBackedStore.cs @@ -11,11 +11,11 @@ namespace osu.Game.Database { protected readonly Storage? Storage; - protected readonly IRealmFactory ContextFactory; + protected readonly IRealmFactory RealmFactory; - protected RealmBackedStore(IRealmFactory contextFactory, Storage? storage = null) + protected RealmBackedStore(IRealmFactory realmFactory, Storage? storage = null) { - ContextFactory = contextFactory; + RealmFactory = realmFactory; Storage = storage; } diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index fa0fecc90c..b6eb28aa33 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -77,7 +77,7 @@ namespace osu.Game.Database try { - context = getContextForCurrentThread(); + context = createContext(); currentWriteTransaction ??= context.BeginWrite(); } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 03da76b330..d5ae4d9bd6 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Database; using osu.Game.Rulesets; +using Realms; namespace osu.Game.Input.Bindings { @@ -22,6 +23,9 @@ namespace osu.Game.Input.Bindings private readonly int? variant; + private IDisposable realmSubscription; + private IQueryable realmKeyBindings; + [Resolved] private RealmKeyBindingStore store { get; set; } @@ -49,35 +53,42 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { + var realm = realmFactory.Get(); + + if (ruleset == null || ruleset.ID.HasValue) + { + var rulesetId = ruleset?.ID; + + realmKeyBindings = realm.All() + .Where(b => b.RulesetID == rulesetId && b.Variant == variant); + + realmSubscription = realmKeyBindings + .SubscribeForNotifications((sender, changes, error) => + { + // first subscription ignored as we are handling this in LoadComplete. + if (changes == null) + return; + + ReloadMappings(); + }); + } + base.LoadComplete(); - store.KeyBindingChanged += ReloadMappings; + } + + protected override void ReloadMappings() + { + if (realmKeyBindings != null) + KeyBindings = realmKeyBindings.Detach(); + else + base.ReloadMappings(); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - if (store != null) - store.KeyBindingChanged -= ReloadMappings; - } - - protected override void ReloadMappings() - { - if (ruleset != null && !ruleset.ID.HasValue) - // if the provided ruleset is not stored to the database, we have no way to retrieve custom bindings. - // fallback to defaults instead. - KeyBindings = DefaultKeyBindings; - else - { - var rulesetId = ruleset?.ID; - - // #1 - KeyBindings = store.Query(rulesetId, variant).Detach(); - - // #2 (Clearly shows lifetime of realm context access) - using (var realm = realmFactory.Get()) - KeyBindings = realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).Detach(); - } + realmSubscription?.Dispose(); } } } diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 8b962e2e9a..0af1beefb7 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -21,8 +21,8 @@ namespace osu.Game.Input /// public event Action? KeyBindingChanged; - public RealmKeyBindingStore(RealmContextFactory contextFactory, Storage? storage = null) - : base(contextFactory, storage) + public RealmKeyBindingStore(RealmContextFactory realmFactory, Storage? storage = null) + : base(realmFactory, storage) { } @@ -57,35 +57,13 @@ namespace osu.Game.Input { var instance = ruleset.CreateInstance(); - using (ContextFactory.GetForWrite()) + using (RealmFactory.GetForWrite()) { foreach (var variant in instance.AvailableVariants) insertDefaults(instance.GetDefaultKeyBindings(variant), ruleset.ID, variant); } } - /// - /// Retrieve all key bindings for the provided specification. - /// - /// An optional ruleset ID. If null, global bindings are returned. - /// An optional ruleset variant. If null, the no-variant bindings are returned. - /// A list of all key bindings found for the query, detached from the database. - public List Query(int? rulesetId = null, int? variant = null) => query(rulesetId, variant).ToList(); - - /// - /// Retrieve all key bindings for the provided action type. - /// - /// The action to lookup. - /// The enum type of the action. - /// A list of all key bindings found for the query, detached from the database. - public List Query(T action) - where T : Enum - { - int lookup = (int)(object)action; - - return query(null, null).Where(rkb => rkb.Action == lookup).ToList(); - } - /// /// Update the database mapping for the provided key binding. /// @@ -96,7 +74,7 @@ namespace osu.Game.Input // the incoming instance could already be a live access object. Live? realmBinding = keyBinding as Live; - using (var realm = ContextFactory.GetForWrite()) + using (var realm = RealmFactory.GetForWrite()) { if (realmBinding == null) { @@ -105,7 +83,7 @@ namespace osu.Game.Input // if neither of the above cases succeeded, retrieve a realm object for further processing. rkb = realm.Context.Find(keyBinding.ID); - realmBinding = new Live(rkb, ContextFactory); + realmBinding = new Live(rkb, RealmFactory); } realmBinding.PerformUpdate(modification); @@ -116,7 +94,7 @@ namespace osu.Game.Input private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) { - using (var usage = ContextFactory.GetForWrite()) + using (var usage = RealmFactory.GetForWrite()) { // compare counts in database vs defaults foreach (var group in defaults.GroupBy(k => k.Action)) @@ -149,6 +127,6 @@ namespace osu.Game.Input /// An optional ruleset ID. If null, global bindings are returned. /// An optional ruleset variant. If null, the no-variant bindings are returned. private IQueryable query(int? rulesetId = null, int? variant = null) => - ContextFactory.Get().All().Where(b => b.RulesetID == rulesetId && b.Variant == variant); + RealmFactory.Get().All().Where(b => b.RulesetID == rulesetId && b.Variant == variant); } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index bdcbf02ee6..b067e50d4d 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using osu.Game.Input; +using osu.Game.Input.Bindings; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osuTK; @@ -33,20 +33,25 @@ namespace osu.Game.Overlays.KeyBinding } [BackgroundDependencyLoader] - private void load(RealmKeyBindingStore store) + private void load(RealmContextFactory realmFactory) { - var bindings = store.Query(Ruleset?.ID, variant).Detach(); + var rulesetId = Ruleset?.ID; - foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) + using (var realm = realmFactory.Get()) { - int intKey = (int)defaultGroup.Key; + var bindings = realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).Detach(); - // one row per valid action. - Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.Action.Equals(intKey))) + foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { - AllowMainMouseButtons = Ruleset != null, - Defaults = defaultGroup.Select(d => d.KeyCombination) - }); + int intKey = (int)defaultGroup.Key; + + // one row per valid action. + Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.Action.Equals(intKey))) + { + AllowMainMouseButtons = Ruleset != null, + Defaults = defaultGroup.Select(d => d.KeyCombination) + }); + } } Add(new ResetButton diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 69e4f734ad..fcb5031657 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,12 +12,12 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Input; using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; @@ -75,7 +74,7 @@ namespace osu.Game.Overlays.Toolbar protected FillFlowContainer Flow; [Resolved] - private RealmKeyBindingStore keyBindings { get; set; } + private RealmContextFactory realmFactory { get; set; } protected ToolbarButton() : base(HoverSampleSet.Loud) @@ -158,32 +157,28 @@ namespace osu.Game.Overlays.Toolbar }; } - private readonly Cached tooltipKeyBinding = new Cached(); + private RealmKeyBinding realmKeyBinding; - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { - keyBindings.KeyBindingChanged += () => tooltipKeyBinding.Invalidate(); - updateKeyBindingTooltip(); - } - - private void updateKeyBindingTooltip() - { - if (tooltipKeyBinding.IsValid) - return; - - keyBindingTooltip.Text = string.Empty; + base.LoadComplete(); if (Hotkey != null) { - KeyCombination? binding = keyBindings.Query(Hotkey.Value).FirstOrDefault()?.KeyCombination; - var keyBindingString = binding?.ReadableString(); + var realm = realmFactory.Get(); + realmKeyBinding = realm.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.Action == (int)Hotkey.Value); - if (!string.IsNullOrEmpty(keyBindingString)) - keyBindingTooltip.Text = $" ({keyBindingString})"; + if (realmKeyBinding != null) + { + realmKeyBinding.PropertyChanged += (sender, args) => + { + if (args.PropertyName == nameof(realmKeyBinding.KeyCombination)) + updateKeyBindingTooltip(); + }; + } + + updateKeyBindingTooltip(); } - - tooltipKeyBinding.Validate(); } protected override bool OnMouseDown(MouseDownEvent e) => true; @@ -224,6 +219,19 @@ namespace osu.Game.Overlays.Toolbar public void OnReleased(GlobalAction action) { } + + private void updateKeyBindingTooltip() + { + if (realmKeyBinding != null) + { + KeyCombination? binding = ((IKeyBinding)realmKeyBinding).KeyCombination; + + var keyBindingString = binding?.ReadableString(); + + if (!string.IsNullOrEmpty(keyBindingString)) + keyBindingTooltip.Text = $" ({keyBindingString})"; + } + } } public class OpaqueBackground : Container From 78707c3b0615d7315289a3a68262fa456017b21e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jan 2021 17:03:02 +0900 Subject: [PATCH 131/520] Remove unused event --- osu.Game/Input/RealmKeyBindingStore.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 0af1beefb7..b42d2688f0 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -16,11 +16,6 @@ namespace osu.Game.Input { public class RealmKeyBindingStore : RealmBackedStore { - /// - /// Fired whenever any key binding change occurs, across all rulesets and types. - /// - public event Action? KeyBindingChanged; - public RealmKeyBindingStore(RealmContextFactory realmFactory, Storage? storage = null) : base(realmFactory, storage) { @@ -88,8 +83,6 @@ namespace osu.Game.Input realmBinding.PerformUpdate(modification); } - - KeyBindingChanged?.Invoke(); } private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) From 542f535247d5d3a4c90570cbbcf0b273a6f7231d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jan 2021 17:34:44 +0900 Subject: [PATCH 132/520] Pull out thread local contexts and have main realm refresh in update loop --- osu.Game/Database/IRealmFactory.cs | 12 ++- osu.Game/Database/RealmContextFactory.cs | 93 +++++++++++------------- osu.Game/OsuGameBase.cs | 1 + 3 files changed, 53 insertions(+), 53 deletions(-) diff --git a/osu.Game/Database/IRealmFactory.cs b/osu.Game/Database/IRealmFactory.cs index 7b126e10ba..0fffc3d7be 100644 --- a/osu.Game/Database/IRealmFactory.cs +++ b/osu.Game/Database/IRealmFactory.cs @@ -7,10 +7,18 @@ namespace osu.Game.Database { public interface IRealmFactory { - Realm Get(); + /// + /// The main realm context, bound to the update thread. + /// + public Realm Context { get; } /// - /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). + /// Get a fresh context for read usage. + /// + Realm GetForRead(); + + /// + /// Request a context for write usage. /// This method may block if a write is already active on a different thread. /// /// A usage containing a usable context. diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index b6eb28aa33..ea3549a9cd 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Threading; +using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; @@ -9,16 +10,13 @@ using Realms; namespace osu.Game.Database { - public class RealmContextFactory : IRealmFactory + public class RealmContextFactory : Component, IRealmFactory { private readonly Storage storage; - private const string database_name = @"client"; private const int schema_version = 5; - private readonly ThreadLocal threadContexts; - /// /// Lock object which is held for the duration of a write operation (via ). /// @@ -32,54 +30,55 @@ namespace osu.Game.Database private Transaction currentWriteTransaction; - public RealmContextFactory(Storage storage) - { - this.storage = storage; - - threadContexts = new ThreadLocal(createContext, true); - - // creating a context will ensure our schema is up-to-date and migrated. - var realm = Get(); - Logger.Log($"Opened realm \"{realm.Config.DatabasePath}\" at version {realm.Config.SchemaVersion}"); - } - - private void onMigration(Migration migration, ulong lastSchemaVersion) - { - } - private static readonly GlobalStatistic reads = GlobalStatistics.Get("Realm", "Get (Read)"); private static readonly GlobalStatistic writes = GlobalStatistics.Get("Realm", "Get (Write)"); + private static readonly GlobalStatistic refreshes = GlobalStatistics.Get("Realm", "Refreshes"); private static readonly GlobalStatistic commits = GlobalStatistics.Get("Realm", "Commits"); private static readonly GlobalStatistic rollbacks = GlobalStatistics.Get("Realm", "Rollbacks"); private static readonly GlobalStatistic contexts_open = GlobalStatistics.Get("Realm", "Contexts (Open)"); private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get("Realm", "Contexts (Created)"); - /// - /// Get a context for the current thread for read-only usage. - /// If a is in progress, the existing write-safe context will be returned. - /// - public Realm Get() + private Realm context; + + public Realm Context { - reads.Value++; - return getContextForCurrentThread(); + get + { + if (context == null) + { + context = createContext(); + Logger.Log($"Opened realm \"{context.Config.DatabasePath}\" at version {context.Config.SchemaVersion}"); + } + + // creating a context will ensure our schema is up-to-date and migrated. + + return context; + } + } + + public RealmContextFactory(Storage storage) + { + this.storage = storage; + } + + public Realm GetForRead() + { + reads.Value++; + return createContext(); } - /// - /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). - /// This method may block if a write is already active on a different thread. - /// - /// A usage containing a usable context. public RealmWriteUsage GetForWrite() { writes.Value++; Monitor.Enter(writeLock); - Realm context; + + Realm realm; try { - context = createContext(); + realm = createContext(); - currentWriteTransaction ??= context.BeginWrite(); + currentWriteTransaction ??= realm.BeginWrite(); } catch { @@ -90,27 +89,15 @@ namespace osu.Game.Database Interlocked.Increment(ref currentWriteUsages); - return new RealmWriteUsage(context, usageCompleted) { IsTransactionLeader = currentWriteTransaction != null && currentWriteUsages == 1 }; + return new RealmWriteUsage(realm, usageCompleted) { IsTransactionLeader = currentWriteTransaction != null && currentWriteUsages == 1 }; } - private Realm getContextForCurrentThread() + protected override void Update() { - var context = threadContexts.Value; + base.Update(); - if (context?.IsClosed != false) - threadContexts.Value = context = createContext(); - - contexts_open.Value = threadContexts.Values.Count; - - if (!refreshCompleted.Value) - { - // to keep things simple, realm refreshes are currently performed per thread context at the point of retrieval. - // in the future this should likely be run as part of the update loop for the main (update thread) context. - context.Refresh(); - refreshCompleted.Value = true; - } - - return context; + if (Context.Refresh()) + refreshes.Value++; } private Realm createContext() @@ -156,5 +143,9 @@ namespace osu.Game.Database Monitor.Exit(writeLock); } } + + private void onMigration(Migration migration, ulong lastSchemaVersion) + { + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 65eca8255e..01f161cfd9 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -170,6 +170,7 @@ namespace osu.Game dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage)); dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); + AddInternal(realmFactory); dependencies.CacheAs(Storage); From 9d744d629f28f85ba8231aa51ff38de441e3fb18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jan 2021 17:35:00 +0900 Subject: [PATCH 133/520] Update existing usages to use the main realm context where applicable --- osu.Game.Tests/Database/TestRealmKeyBindingStore.cs | 2 +- osu.Game/Database/Live.cs | 2 +- .../Input/Bindings/DatabasedKeyBindingContainer.cs | 6 ++---- osu.Game/Input/RealmKeyBindingStore.cs | 12 ++---------- .../Overlays/KeyBinding/KeyBindingsSubsection.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 3 +-- 6 files changed, 8 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 58633e2f03..691c55c601 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Database Assert.That(query().Where(k => k.Action == (int)GlobalAction.Select).Count, Is.EqualTo(2)); } - private IQueryable query() => realmContextFactory.Get().All(); + private IQueryable query() => realmContextFactory.Context.All(); [Test] public void TestUpdateViaQueriedReference() diff --git a/osu.Game/Database/Live.cs b/osu.Game/Database/Live.cs index 24a2aa258b..49218ddd6e 100644 --- a/osu.Game/Database/Live.cs +++ b/osu.Game/Database/Live.cs @@ -55,7 +55,7 @@ namespace osu.Game.Database private T getThreadLocalValue() { - var context = contextFactory.Get(); + var context = contextFactory.Context; // only use the original if no context is available or the source realm is the same. if (context == null || original.Realm?.IsSameInstance(context) == true) return original; diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index d5ae4d9bd6..04f050f536 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -53,14 +53,12 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - var realm = realmFactory.Get(); - if (ruleset == null || ruleset.ID.HasValue) { var rulesetId = ruleset?.ID; - realmKeyBindings = realm.All() - .Where(b => b.RulesetID == rulesetId && b.Variant == variant); + realmKeyBindings = realmFactory.Context.All() + .Where(b => b.RulesetID == rulesetId && b.Variant == variant); realmSubscription = realmKeyBindings .SubscribeForNotifications((sender, changes, error) => diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index b42d2688f0..dd487af9bc 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -28,7 +28,7 @@ namespace osu.Game.Input /// A set of display strings for all the user's key configuration for the action. public IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction) { - foreach (var action in query().Where(b => (GlobalAction)b.Action == globalAction)) + foreach (var action in RealmFactory.Context.All().Where(b => (GlobalAction)b.Action == globalAction)) { string str = ((IKeyBinding)action).KeyCombination.ReadableString(); @@ -92,7 +92,7 @@ namespace osu.Game.Input // compare counts in database vs defaults foreach (var group in defaults.GroupBy(k => k.Action)) { - int count = query(rulesetId, variant).Count(k => k.Action == (int)group.Key); + int count = usage.Context.All().Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.Action == (int)group.Key); int aimCount = group.Count(); if (aimCount <= count) @@ -113,13 +113,5 @@ namespace osu.Game.Input } } } - - /// - /// Retrieve live queryable s for a specified ruleset/variant content. - /// - /// An optional ruleset ID. If null, global bindings are returned. - /// An optional ruleset variant. If null, the no-variant bindings are returned. - private IQueryable query(int? rulesetId = null, int? variant = null) => - RealmFactory.Get().All().Where(b => b.RulesetID == rulesetId && b.Variant == variant); } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index b067e50d4d..0f95d07da8 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.KeyBinding { var rulesetId = Ruleset?.ID; - using (var realm = realmFactory.Get()) + using (var realm = realmFactory.GetForRead()) { var bindings = realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).Detach(); diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index fcb5031657..7bb0eb894c 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -165,8 +165,7 @@ namespace osu.Game.Overlays.Toolbar if (Hotkey != null) { - var realm = realmFactory.Get(); - realmKeyBinding = realm.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.Action == (int)Hotkey.Value); + realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.Action == (int)Hotkey.Value); if (realmKeyBinding != null) { From 9086d7554270658fc70a12c0327f77c180bc05aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jan 2021 17:59:47 +0900 Subject: [PATCH 134/520] Update write usages --- .../Database/TestRealmKeyBindingStore.cs | 7 +++- osu.Game/Input/RealmKeyBindingStore.cs | 26 ------------- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 39 +++++++++++++------ .../KeyBinding/KeyBindingsSubsection.cs | 2 +- 4 files changed, 34 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 691c55c601..426593f5de 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -62,7 +62,12 @@ namespace osu.Game.Tests.Database Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape })); - keyBindingStore.Update(backBinding, binding => binding.KeyCombination = new KeyCombination(InputKey.BackSpace)); + var binding = backBinding; + + realmContextFactory.Context.Write(() => + { + ((IKeyBinding)binding).KeyCombination = new KeyCombination(InputKey.BackSpace); + }); Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index dd487af9bc..478f4792f8 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -59,32 +59,6 @@ namespace osu.Game.Input } } - /// - /// Update the database mapping for the provided key binding. - /// - /// The key binding to update. Can be detached from the database. - /// The modification to apply to the key binding. - public void Update(IHasGuidPrimaryKey keyBinding, Action modification) - { - // the incoming instance could already be a live access object. - Live? realmBinding = keyBinding as Live; - - using (var realm = RealmFactory.GetForWrite()) - { - if (realmBinding == null) - { - // the incoming instance could be a raw realm object. - if (!(keyBinding is RealmKeyBinding rkb)) - // if neither of the above cases succeeded, retrieve a realm object for further processing. - rkb = realm.Context.Find(keyBinding.ID); - - realmBinding = new Live(rkb, RealmFactory); - } - - realmBinding.PerformUpdate(modification); - } - } - private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) { using (var usage = RealmFactory.GetForWrite()) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 0a065c9dbc..f73d92f5c2 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.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; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -16,7 +17,7 @@ using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Input; +using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -26,7 +27,7 @@ namespace osu.Game.Overlays.KeyBinding public class KeyBindingRow : Container, IFilterable { private readonly object action; - private readonly IEnumerable bindings; + private readonly IEnumerable bindings; private const float transition_time = 150; @@ -52,9 +53,9 @@ namespace osu.Game.Overlays.KeyBinding private FillFlowContainer cancelAndClearButtons; private FillFlowContainer buttons; - public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend((string)text.Text); + public IEnumerable FilterTerms => bindings.Select(b => ((IKeyBinding)b).KeyCombination.ReadableString()).Prepend((string)text.Text); - public KeyBindingRow(object action, IEnumerable bindings) + public KeyBindingRow(object action, List bindings) { this.action = action; this.bindings = bindings; @@ -67,7 +68,7 @@ namespace osu.Game.Overlays.KeyBinding } [Resolved] - private RealmKeyBindingStore store { get; set; } + private RealmContextFactory realmFactory { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -127,7 +128,12 @@ namespace osu.Game.Overlays.KeyBinding { var button = buttons[i++]; button.UpdateKeyCombination(d); - store.Update((IHasGuidPrimaryKey)button.KeyBinding, k => k.KeyCombination = button.KeyBinding.KeyCombination); + + using (var write = realmFactory.GetForWrite()) + { + var binding = write.Context.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); + binding.KeyCombination = button.KeyBinding.KeyCombination; + } } } @@ -286,7 +292,11 @@ namespace osu.Game.Overlays.KeyBinding { if (bindTarget != null) { - store.Update((IHasGuidPrimaryKey)bindTarget.KeyBinding, k => k.KeyCombination = bindTarget.KeyBinding.KeyCombination); + using (var write = realmFactory.GetForWrite()) + { + var binding = write.Context.Find(((IHasGuidPrimaryKey)bindTarget.KeyBinding).ID); + binding.KeyCombination = bindTarget.KeyBinding.KeyCombination; + } bindTarget.IsBinding = false; Schedule(() => @@ -360,7 +370,7 @@ namespace osu.Game.Overlays.KeyBinding public class KeyButton : Container { - public readonly IKeyBinding KeyBinding; + public readonly RealmKeyBinding KeyBinding; private readonly Box box; public readonly OsuSpriteText Text; @@ -382,8 +392,11 @@ namespace osu.Game.Overlays.KeyBinding } } - public KeyButton(IKeyBinding keyBinding) + public KeyButton(RealmKeyBinding keyBinding) { + if (keyBinding.IsManaged) + throw new ArgumentException("Key binding should not be attached as we make temporary changes", nameof(keyBinding)); + KeyBinding = keyBinding; Margin = new MarginPadding(padding); @@ -416,7 +429,7 @@ namespace osu.Game.Overlays.KeyBinding Margin = new MarginPadding(5), Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = keyBinding.KeyCombination.ReadableString(), + Text = ((IKeyBinding)keyBinding).KeyCombination.ReadableString(), }, }; } @@ -455,8 +468,10 @@ namespace osu.Game.Overlays.KeyBinding public void UpdateKeyCombination(KeyCombination newCombination) { - KeyBinding.KeyCombination = newCombination; - Text.Text = KeyBinding.KeyCombination.ReadableString(); + var keyBinding = (IKeyBinding)KeyBinding; + + keyBinding.KeyCombination = newCombination; + Text.Text = keyBinding.KeyCombination.ReadableString(); } } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index 0f95d07da8..a23f22cf57 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.KeyBinding int intKey = (int)defaultGroup.Key; // one row per valid action. - Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.Action.Equals(intKey))) + Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.Action.Equals(intKey)).ToList()) { AllowMainMouseButtons = Ruleset != null, Defaults = defaultGroup.Select(d => d.KeyCombination) From fcb4a53f37250cd4a7f9ac7b900747d4b7481f4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jan 2021 18:07:35 +0900 Subject: [PATCH 135/520] Rename realm persisted properties to avoid casting necessity --- .../Database/TestRealmKeyBindingStore.cs | 16 +++++++-------- osu.Game/Input/Bindings/RealmKeyBinding.cs | 20 ++++++++++--------- osu.Game/Input/RealmKeyBindingStore.cs | 10 +++++----- osu.Game/OsuGameBase.cs | 4 ++-- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 14 ++++++------- .../KeyBinding/KeyBindingsSubsection.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 6 +++--- 7 files changed, 36 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 426593f5de..1a7e0d67c5 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -45,8 +45,8 @@ namespace osu.Game.Tests.Database Assert.That(query().Count, Is.EqualTo(3)); - Assert.That(query().Where(k => k.Action == (int)GlobalAction.Back).Count, Is.EqualTo(1)); - Assert.That(query().Where(k => k.Action == (int)GlobalAction.Select).Count, Is.EqualTo(2)); + Assert.That(query().Where(k => k.ActionInt == (int)GlobalAction.Back).Count, Is.EqualTo(1)); + Assert.That(query().Where(k => k.ActionInt == (int)GlobalAction.Select).Count, Is.EqualTo(2)); } private IQueryable query() => realmContextFactory.Context.All(); @@ -58,22 +58,22 @@ namespace osu.Game.Tests.Database keyBindingStore.Register(testContainer); - var backBinding = query().Single(k => k.Action == (int)GlobalAction.Back); + var backBinding = query().Single(k => k.ActionInt == (int)GlobalAction.Back); - Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape })); + Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape })); var binding = backBinding; realmContextFactory.Context.Write(() => { - ((IKeyBinding)binding).KeyCombination = new KeyCombination(InputKey.BackSpace); + binding.KeyCombination = new KeyCombination(InputKey.BackSpace); }); - Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); + Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); // check still correct after re-query. - backBinding = query().Single(k => k.Action == (int)GlobalAction.Back); - Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); + backBinding = query().Single(k => k.ActionInt == (int)GlobalAction.Back); + Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); } [TearDown] diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs index 1e690ddbab..ecffc1fd62 100644 --- a/osu.Game/Input/Bindings/RealmKeyBinding.cs +++ b/osu.Game/Input/Bindings/RealmKeyBinding.cs @@ -7,7 +7,7 @@ using Realms; namespace osu.Game.Input.Bindings { - [MapTo("KeyBinding")] + [MapTo(nameof(KeyBinding))] public class RealmKeyBinding : RealmObject, IHasGuidPrimaryKey, IKeyBinding { [PrimaryKey] @@ -17,20 +17,22 @@ namespace osu.Game.Input.Bindings public int? Variant { get; set; } - KeyCombination IKeyBinding.KeyCombination + public KeyCombination KeyCombination { - get => KeyCombination; - set => KeyCombination = value.ToString(); + get => KeyCombinationString; + set => KeyCombinationString = value.ToString(); } - object IKeyBinding.Action + public object Action { - get => Action; - set => Action = (int)value; + get => ActionInt; + set => ActionInt = (int)value; } - public int Action { get; set; } + [MapTo(nameof(Action))] + public int ActionInt { get; set; } - public string KeyCombination { get; set; } + [MapTo(nameof(KeyCombination))] + public string KeyCombinationString { get; set; } } } diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 478f4792f8..8e43811c36 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -28,9 +28,9 @@ namespace osu.Game.Input /// A set of display strings for all the user's key configuration for the action. public IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction) { - foreach (var action in RealmFactory.Context.All().Where(b => (GlobalAction)b.Action == globalAction)) + foreach (var action in RealmFactory.Context.All().Where(b => (GlobalAction)b.ActionInt == globalAction)) { - string str = ((IKeyBinding)action).KeyCombination.ReadableString(); + string str = action.KeyCombination.ReadableString(); // even if found, the readable string may be empty for an unbound action. if (str.Length > 0) @@ -66,7 +66,7 @@ namespace osu.Game.Input // compare counts in database vs defaults foreach (var group in defaults.GroupBy(k => k.Action)) { - int count = usage.Context.All().Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.Action == (int)group.Key); + int count = usage.Context.All().Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)group.Key); int aimCount = group.Count(); if (aimCount <= count) @@ -78,8 +78,8 @@ namespace osu.Game.Input usage.Context.Add(new RealmKeyBinding { ID = Guid.NewGuid().ToString(), - KeyCombination = insertable.KeyCombination.ToString(), - Action = (int)insertable.Action, + KeyCombinationString = insertable.KeyCombination.ToString(), + ActionInt = (int)insertable.Action, RulesetID = rulesetId, Variant = variant }); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 01f161cfd9..192867b8c8 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -342,8 +342,8 @@ namespace osu.Game realm.Context.Add(new RealmKeyBinding { ID = Guid.NewGuid().ToString(), - KeyCombination = dkb.KeyCombination.ToString(), - Action = (int)dkb.Action, + KeyCombinationString = dkb.KeyCombination.ToString(), + ActionInt = (int)dkb.Action, RulesetID = dkb.RulesetID, Variant = dkb.Variant }); diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index f73d92f5c2..34cdfd18fa 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.KeyBinding private FillFlowContainer cancelAndClearButtons; private FillFlowContainer buttons; - public IEnumerable FilterTerms => bindings.Select(b => ((IKeyBinding)b).KeyCombination.ReadableString()).Prepend((string)text.Text); + public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend((string)text.Text); public KeyBindingRow(object action, List bindings) { @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.KeyBinding using (var write = realmFactory.GetForWrite()) { var binding = write.Context.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); - binding.KeyCombination = button.KeyBinding.KeyCombination; + binding.KeyCombinationString = button.KeyBinding.KeyCombinationString; } } } @@ -295,7 +295,7 @@ namespace osu.Game.Overlays.KeyBinding using (var write = realmFactory.GetForWrite()) { var binding = write.Context.Find(((IHasGuidPrimaryKey)bindTarget.KeyBinding).ID); - binding.KeyCombination = bindTarget.KeyBinding.KeyCombination; + binding.KeyCombinationString = bindTarget.KeyBinding.KeyCombinationString; } bindTarget.IsBinding = false; @@ -429,7 +429,7 @@ namespace osu.Game.Overlays.KeyBinding Margin = new MarginPadding(5), Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = ((IKeyBinding)keyBinding).KeyCombination.ReadableString(), + Text = keyBinding.KeyCombination.ReadableString(), }, }; } @@ -468,10 +468,8 @@ namespace osu.Game.Overlays.KeyBinding public void UpdateKeyCombination(KeyCombination newCombination) { - var keyBinding = (IKeyBinding)KeyBinding; - - keyBinding.KeyCombination = newCombination; - Text.Text = keyBinding.KeyCombination.ReadableString(); + KeyBinding.KeyCombination = newCombination; + Text.Text = KeyBinding.KeyCombination.ReadableString(); } } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index a23f22cf57..fae42f5492 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.KeyBinding int intKey = (int)defaultGroup.Key; // one row per valid action. - Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.Action.Equals(intKey)).ToList()) + Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.ActionInt.Equals(intKey)).ToList()) { AllowMainMouseButtons = Ruleset != null, Defaults = defaultGroup.Select(d => d.KeyCombination) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 7bb0eb894c..305a17126a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -165,13 +165,13 @@ namespace osu.Game.Overlays.Toolbar if (Hotkey != null) { - realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.Action == (int)Hotkey.Value); + realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.ActionInt == (int)Hotkey.Value); if (realmKeyBinding != null) { realmKeyBinding.PropertyChanged += (sender, args) => { - if (args.PropertyName == nameof(realmKeyBinding.KeyCombination)) + if (args.PropertyName == nameof(realmKeyBinding.KeyCombinationString)) updateKeyBindingTooltip(); }; } @@ -223,7 +223,7 @@ namespace osu.Game.Overlays.Toolbar { if (realmKeyBinding != null) { - KeyCombination? binding = ((IKeyBinding)realmKeyBinding).KeyCombination; + KeyCombination? binding = realmKeyBinding.KeyCombination; var keyBindingString = binding?.ReadableString(); From 5fa3a22f28ac40a857b1a21b49c13766ee8e752d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jan 2021 18:13:30 +0900 Subject: [PATCH 136/520] Remove unused RealmBackedStore base class --- osu.Game/Database/RealmBackedStore.cs | 29 -------------------------- osu.Game/Input/RealmKeyBindingStore.cs | 15 ++++++------- 2 files changed, 8 insertions(+), 36 deletions(-) delete mode 100644 osu.Game/Database/RealmBackedStore.cs diff --git a/osu.Game/Database/RealmBackedStore.cs b/osu.Game/Database/RealmBackedStore.cs deleted file mode 100644 index bc67e332fe..0000000000 --- a/osu.Game/Database/RealmBackedStore.cs +++ /dev/null @@ -1,29 +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 osu.Framework.Platform; - -#nullable enable - -namespace osu.Game.Database -{ - public abstract class RealmBackedStore - { - protected readonly Storage? Storage; - - protected readonly IRealmFactory RealmFactory; - - protected RealmBackedStore(IRealmFactory realmFactory, Storage? storage = null) - { - RealmFactory = realmFactory; - Storage = storage; - } - - /// - /// Perform any common clean-up tasks. Should be run when idle, or whenever necessary. - /// - public virtual void Cleanup() - { - } - } -} diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 8e43811c36..756df6434e 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Input.Bindings; -using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Input.Bindings; using osu.Game.Rulesets; @@ -14,11 +13,13 @@ using osu.Game.Rulesets; namespace osu.Game.Input { - public class RealmKeyBindingStore : RealmBackedStore + public class RealmKeyBindingStore { - public RealmKeyBindingStore(RealmContextFactory realmFactory, Storage? storage = null) - : base(realmFactory, storage) + private readonly RealmContextFactory realmFactory; + + public RealmKeyBindingStore(RealmContextFactory realmFactory) { + this.realmFactory = realmFactory; } /// @@ -28,7 +29,7 @@ namespace osu.Game.Input /// A set of display strings for all the user's key configuration for the action. public IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction) { - foreach (var action in RealmFactory.Context.All().Where(b => (GlobalAction)b.ActionInt == globalAction)) + foreach (var action in realmFactory.Context.All().Where(b => (GlobalAction)b.ActionInt == globalAction)) { string str = action.KeyCombination.ReadableString(); @@ -52,7 +53,7 @@ namespace osu.Game.Input { var instance = ruleset.CreateInstance(); - using (RealmFactory.GetForWrite()) + using (realmFactory.GetForWrite()) { foreach (var variant in instance.AvailableVariants) insertDefaults(instance.GetDefaultKeyBindings(variant), ruleset.ID, variant); @@ -61,7 +62,7 @@ namespace osu.Game.Input private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) { - using (var usage = RealmFactory.GetForWrite()) + using (var usage = realmFactory.GetForWrite()) { // compare counts in database vs defaults foreach (var group in defaults.GroupBy(k => k.Action)) From 8442b34e84032f9365a6575cece19c86690999dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jan 2021 18:24:19 +0900 Subject: [PATCH 137/520] Tidy up write usage class --- osu.Game/Database/IRealmFactory.cs | 2 +- osu.Game/Database/RealmContextFactory.cs | 81 ++++++------------------ osu.Game/Database/RealmWriteUsage.cs | 55 ---------------- 3 files changed, 22 insertions(+), 116 deletions(-) delete mode 100644 osu.Game/Database/RealmWriteUsage.cs diff --git a/osu.Game/Database/IRealmFactory.cs b/osu.Game/Database/IRealmFactory.cs index 0fffc3d7be..4a92a5683b 100644 --- a/osu.Game/Database/IRealmFactory.cs +++ b/osu.Game/Database/IRealmFactory.cs @@ -22,6 +22,6 @@ namespace osu.Game.Database /// This method may block if a write is already active on a different thread. /// /// A usage containing a usable context. - RealmWriteUsage GetForWrite(); + RealmContextFactory.RealmWriteUsage GetForWrite(); } } diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index ea3549a9cd..dc8761fb3c 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Threading; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Platform; @@ -13,6 +14,7 @@ namespace osu.Game.Database public class RealmContextFactory : Component, IRealmFactory { private readonly Storage storage; + private const string database_name = @"client"; private const int schema_version = 5; @@ -22,20 +24,11 @@ namespace osu.Game.Database /// private readonly object writeLock = new object(); - private ThreadLocal refreshCompleted = new ThreadLocal(); - - private bool rollbackRequired; - private int currentWriteUsages; - private Transaction currentWriteTransaction; - private static readonly GlobalStatistic reads = GlobalStatistics.Get("Realm", "Get (Read)"); private static readonly GlobalStatistic writes = GlobalStatistics.Get("Realm", "Get (Write)"); - private static readonly GlobalStatistic refreshes = GlobalStatistics.Get("Realm", "Refreshes"); - private static readonly GlobalStatistic commits = GlobalStatistics.Get("Realm", "Commits"); - private static readonly GlobalStatistic rollbacks = GlobalStatistics.Get("Realm", "Rollbacks"); - private static readonly GlobalStatistic contexts_open = GlobalStatistics.Get("Realm", "Contexts (Open)"); + private static readonly GlobalStatistic refreshes = GlobalStatistics.Get("Realm", "Dirty Refreshes"); private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get("Realm", "Contexts (Created)"); private Realm context; @@ -72,24 +65,8 @@ namespace osu.Game.Database writes.Value++; Monitor.Enter(writeLock); - Realm realm; - - try - { - realm = createContext(); - - currentWriteTransaction ??= realm.BeginWrite(); - } - catch - { - // retrieval of a context could trigger a fatal error. - Monitor.Exit(writeLock); - throw; - } - Interlocked.Increment(ref currentWriteUsages); - - return new RealmWriteUsage(realm, usageCompleted) { IsTransactionLeader = currentWriteTransaction != null && currentWriteUsages == 1 }; + return new RealmWriteUsage(this); } protected override void Update() @@ -111,41 +88,25 @@ namespace osu.Game.Database }); } - private void usageCompleted(RealmWriteUsage usage) - { - int usages = Interlocked.Decrement(ref currentWriteUsages); - - try - { - rollbackRequired |= usage.RollbackRequired; - - if (usages == 0) - { - if (rollbackRequired) - { - rollbacks.Value++; - currentWriteTransaction?.Rollback(); - } - else - { - commits.Value++; - currentWriteTransaction?.Commit(); - } - - currentWriteTransaction = null; - rollbackRequired = false; - - refreshCompleted = new ThreadLocal(); - } - } - finally - { - Monitor.Exit(writeLock); - } - } - private void onMigration(Migration migration, ulong lastSchemaVersion) { } + + public class RealmWriteUsage : InvokeOnDisposal + { + public readonly Realm Context; + + public RealmWriteUsage(RealmContextFactory factory) + : base(factory, usageCompleted) + { + Context = factory.createContext(); + Context.BeginWrite(); + } + + private static void usageCompleted(RealmContextFactory factory) + { + Monitor.Exit(factory.writeLock); + } + } } } diff --git a/osu.Game/Database/RealmWriteUsage.cs b/osu.Game/Database/RealmWriteUsage.cs deleted file mode 100644 index 35e30e8123..0000000000 --- a/osu.Game/Database/RealmWriteUsage.cs +++ /dev/null @@ -1,55 +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 Realms; - -namespace osu.Game.Database -{ - public class RealmWriteUsage : IDisposable - { - public readonly Realm Context; - private readonly Action usageCompleted; - - public bool RollbackRequired { get; private set; } - - public RealmWriteUsage(Realm context, Action onCompleted) - { - Context = context; - usageCompleted = onCompleted; - } - - /// - /// Whether this write usage will commit a transaction on completion. - /// If false, there is a parent usage responsible for transaction commit. - /// - public bool IsTransactionLeader; - - private bool isDisposed; - - protected void Dispose(bool disposing) - { - if (isDisposed) return; - - isDisposed = true; - - usageCompleted?.Invoke(this); - } - - public void Rollback(Exception error = null) - { - RollbackRequired = true; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - ~RealmWriteUsage() - { - Dispose(false); - } - } -} From 9bf9a8c351ac533bbdd4b33a7f1659dd9544168f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jan 2021 18:36:05 +0900 Subject: [PATCH 138/520] Remove Live<> wrapper until it is needed --- osu.Game/Database/Live.cs | 104 --------------------------- osu.Game/Database/RealmExtensions.cs | 27 ------- 2 files changed, 131 deletions(-) delete mode 100644 osu.Game/Database/Live.cs diff --git a/osu.Game/Database/Live.cs b/osu.Game/Database/Live.cs deleted file mode 100644 index 49218ddd6e..0000000000 --- a/osu.Game/Database/Live.cs +++ /dev/null @@ -1,104 +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 System.Threading; -using Realms; - -#nullable enable - -namespace osu.Game.Database -{ - /// - /// Provides a method of passing realm live objects across threads in a safe fashion. - /// - /// - /// To consume this as a live instance, the live object should be stored and accessed via each time. - /// To consume this as a detached instance, assign to a variable of type . The implicit conversion will handle detaching an instance. - /// - /// The underlying object type. Should be a with a primary key provided via . - public class Live : IEquatable>, IHasGuidPrimaryKey - where T : RealmObject, IHasGuidPrimaryKey - { - /// - /// The primary key of the object. - /// - public Guid Guid { get; } - - public string ID - { - get => Guid.ToString(); - set => throw new NotImplementedException(); - } - - private readonly ThreadLocal threadValues; - - private readonly T original; - - private readonly IRealmFactory contextFactory; - - public Live(T item, IRealmFactory contextFactory) - { - this.contextFactory = contextFactory; - - original = item; - Guid = item.Guid; - - threadValues = new ThreadLocal(getThreadLocalValue); - - // the instance passed in may not be in a managed state. - // for now let's immediately retrieve a managed object on the current thread. - // in the future we may want to delay this until the first access (only populating the Guid at construction time). - if (!item.IsManaged) - original = Get(); - } - - private T getThreadLocalValue() - { - var context = contextFactory.Context; - - // only use the original if no context is available or the source realm is the same. - if (context == null || original.Realm?.IsSameInstance(context) == true) return original; - - return context.Find(ID); - } - - /// - /// Retrieve a live reference to the data. - /// - public T Get() => threadValues.Value; - - /// - /// Retrieve a detached copy of the data. - /// - public T Detach() => Get().Detach(); - - /// - /// Wrap a property of this instance as its own live access object. - /// - /// The child to return. - /// The underlying child object type. Should be a with a primary key provided via . - /// A wrapped instance of the child. - public Live WrapChild(Func lookup) - where TChild : RealmObject, IHasGuidPrimaryKey => new Live(lookup(Get()), contextFactory); - - /// - /// Perform a write operation on this live object. - /// - /// The action to perform. - public void PerformUpdate(Action perform) - { - using (contextFactory.GetForWrite()) - perform(Get()); - } - - public static implicit operator T?(Live? wrapper) - => wrapper?.Detach() ?? null; - - public static implicit operator Live(T obj) => obj.WrapAsUnmanaged(); - - public bool Equals(Live? other) => other != null && other.Guid == Guid; - - public override string ToString() => Get().ToString(); - } -} diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs index b25299bf23..07d397ca6c 100644 --- a/osu.Game/Database/RealmExtensions.cs +++ b/osu.Game/Database/RealmExtensions.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 System; using System.Collections.Generic; using AutoMapper; using osu.Game.Beatmaps; @@ -69,31 +68,5 @@ namespace osu.Game.Database return mapper.Map(item); } - - /// - /// Wrap a managed instance of a realm object in a . - /// - /// The item to wrap. - /// A factory to retrieve realm contexts from. - /// The type of object. - /// A wrapped instance of the provided item. - public static Live Wrap(this T item, IRealmFactory contextFactory) - where T : RealmObject, IHasGuidPrimaryKey => new Live(item, contextFactory); - - /// - /// Wrap an unmanaged instance of a realm object in a . - /// - /// The item to wrap. - /// The type of object. - /// A wrapped instance of the provided item. - /// Throws if the provided item is managed. - public static Live WrapAsUnmanaged(this T item) - where T : RealmObject, IHasGuidPrimaryKey - { - if (item.IsManaged) - throw new ArgumentException("Provided item must not be managed", nameof(item)); - - return new Live(item, null); - } } } From 674e78fd93858e00ff4f54b930c70d5172ca528f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jan 2021 18:38:30 +0900 Subject: [PATCH 139/520] Fix broken xmldoc --- osu.Game/Database/RealmExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs index 07d397ca6c..04b0820dd9 100644 --- a/osu.Game/Database/RealmExtensions.cs +++ b/osu.Game/Database/RealmExtensions.cs @@ -56,7 +56,7 @@ namespace osu.Game.Database } /// - /// Create a detached copy of the each item in the list. + /// Create a detached copy of the item. /// /// The managed to detach. /// The type of object. From 4759797d152c4eeabf128671f5a49f7fd0725c6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jan 2021 15:51:07 +0900 Subject: [PATCH 140/520] 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 492c88c7e4..8a74f8ddce 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1a762be9c9..e43da1df7b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -27,7 +27,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 93be3645ee..aecc8cd5eb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - + From af1509d892b0788cd6458865d1c004919d560091 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jan 2021 15:51:19 +0900 Subject: [PATCH 141/520] Remove unused variable (but add back pending writes counter) --- osu.Game/Database/RealmContextFactory.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index dc8761fb3c..c18cd31bfa 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -24,12 +24,11 @@ namespace osu.Game.Database /// private readonly object writeLock = new object(); - private int currentWriteUsages; - private static readonly GlobalStatistic reads = GlobalStatistics.Get("Realm", "Get (Read)"); private static readonly GlobalStatistic writes = GlobalStatistics.Get("Realm", "Get (Write)"); private static readonly GlobalStatistic refreshes = GlobalStatistics.Get("Realm", "Dirty Refreshes"); private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get("Realm", "Contexts (Created)"); + private static readonly GlobalStatistic pending_writes = GlobalStatistics.Get("Realm", "Pending writes"); private Realm context; @@ -63,9 +62,10 @@ namespace osu.Game.Database public RealmWriteUsage GetForWrite() { writes.Value++; + pending_writes.Value++; + Monitor.Enter(writeLock); - Interlocked.Increment(ref currentWriteUsages); return new RealmWriteUsage(this); } @@ -106,6 +106,7 @@ namespace osu.Game.Database private static void usageCompleted(RealmContextFactory factory) { Monitor.Exit(factory.writeLock); + pending_writes.Value--; } } } From 8a08d3f4efe807ca77f0d4fe893962a3d6ec9bfc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jan 2021 16:13:10 +0900 Subject: [PATCH 142/520] Fix transactions not actually being committed --- osu.Game/Database/RealmContextFactory.cs | 39 +++++++++++++++---- osu.Game/Input/RealmKeyBindingStore.cs | 13 +++---- osu.Game/OsuGameBase.cs | 8 ++-- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 10 +++-- 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index c18cd31bfa..0631acc750 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -1,8 +1,8 @@ // 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.Threading; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Platform; @@ -92,19 +92,42 @@ namespace osu.Game.Database { } - public class RealmWriteUsage : InvokeOnDisposal + /// + /// A transaction used for making changes to realm data. + /// + public class RealmWriteUsage : IDisposable { - public readonly Realm Context; + public readonly Realm Realm; - public RealmWriteUsage(RealmContextFactory factory) - : base(factory, usageCompleted) + private readonly RealmContextFactory factory; + private readonly Transaction transaction; + + internal RealmWriteUsage(RealmContextFactory factory) { - Context = factory.createContext(); - Context.BeginWrite(); + this.factory = factory; + + Realm = factory.createContext(); + transaction = Realm.BeginWrite(); } - private static void usageCompleted(RealmContextFactory factory) + /// + /// Commit all changes made in this transaction. + /// + public void Commit() => transaction.Commit(); + + /// + /// Revert all changes made in this transaction. + /// + public void Rollback() => transaction.Rollback(); + + /// + /// Disposes this instance, calling the initially captured action. + /// + public virtual void Dispose() { + // rollback if not explicitly committed. + transaction?.Dispose(); + Monitor.Exit(factory.writeLock); pending_writes.Value--; } diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 756df6434e..d55d2362fe 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -53,11 +53,8 @@ namespace osu.Game.Input { var instance = ruleset.CreateInstance(); - using (realmFactory.GetForWrite()) - { - foreach (var variant in instance.AvailableVariants) - insertDefaults(instance.GetDefaultKeyBindings(variant), ruleset.ID, variant); - } + foreach (var variant in instance.AvailableVariants) + insertDefaults(instance.GetDefaultKeyBindings(variant), ruleset.ID, variant); } private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) @@ -67,7 +64,7 @@ namespace osu.Game.Input // compare counts in database vs defaults foreach (var group in defaults.GroupBy(k => k.Action)) { - int count = usage.Context.All().Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)group.Key); + int count = usage.Realm.All().Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)group.Key); int aimCount = group.Count(); if (aimCount <= count) @@ -76,7 +73,7 @@ namespace osu.Game.Input foreach (var insertable in group.Skip(count).Take(aimCount - count)) { // insert any defaults which are missing. - usage.Context.Add(new RealmKeyBinding + usage.Realm.Add(new RealmKeyBinding { ID = Guid.NewGuid().ToString(), KeyCombinationString = insertable.KeyCombination.ToString(), @@ -86,6 +83,8 @@ namespace osu.Game.Input }); } } + + usage.Commit(); } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 192867b8c8..a755fdb379 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -330,16 +330,16 @@ namespace osu.Game private void migrateDataToRealm() { using (var db = contextFactory.GetForWrite()) - using (var realm = realmFactory.GetForWrite()) + using (var usage = realmFactory.GetForWrite()) { var existingBindings = db.Context.DatabasedKeyBinding; // only migrate data if the realm database is empty. - if (!realm.Context.All().Any()) + if (!usage.Realm.All().Any()) { foreach (var dkb in existingBindings) { - realm.Context.Add(new RealmKeyBinding + usage.Realm.Add(new RealmKeyBinding { ID = Guid.NewGuid().ToString(), KeyCombinationString = dkb.KeyCombination.ToString(), @@ -351,6 +351,8 @@ namespace osu.Game } db.Context.RemoveRange(existingBindings); + + usage.Commit(); } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 34cdfd18fa..bfabc8008d 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -129,10 +129,12 @@ namespace osu.Game.Overlays.KeyBinding var button = buttons[i++]; button.UpdateKeyCombination(d); - using (var write = realmFactory.GetForWrite()) + using (var usage = realmFactory.GetForWrite()) { - var binding = write.Context.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); + var binding = usage.Realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); binding.KeyCombinationString = button.KeyBinding.KeyCombinationString; + + usage.Commit(); } } } @@ -294,8 +296,10 @@ namespace osu.Game.Overlays.KeyBinding { using (var write = realmFactory.GetForWrite()) { - var binding = write.Context.Find(((IHasGuidPrimaryKey)bindTarget.KeyBinding).ID); + var binding = write.Realm.Find(((IHasGuidPrimaryKey)bindTarget.KeyBinding).ID); binding.KeyCombinationString = bindTarget.KeyBinding.KeyCombinationString; + + write.Commit(); } bindTarget.IsBinding = false; From e3c5a909e4a641c4d79aa0da889e1f921b063b31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jan 2021 16:30:57 +0900 Subject: [PATCH 143/520] Fix known non-null variable --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 305a17126a..0f270cbece 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -223,9 +223,7 @@ namespace osu.Game.Overlays.Toolbar { if (realmKeyBinding != null) { - KeyCombination? binding = realmKeyBinding.KeyCombination; - - var keyBindingString = binding?.ReadableString(); + var keyBindingString = realmKeyBinding.KeyCombination.ReadableString(); if (!string.IsNullOrEmpty(keyBindingString)) keyBindingTooltip.Text = $" ({keyBindingString})"; From df08d964a5c3894224f59c1d0562e107767cb062 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jan 2021 16:31:18 +0900 Subject: [PATCH 144/520] Mark the types which have been migrated in OsuDbContext --- osu.Game/Database/OsuDbContext.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 2ae07b3cf8..8ca65525db 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -24,13 +24,15 @@ namespace osu.Game.Database public DbSet BeatmapDifficulty { get; set; } public DbSet BeatmapMetadata { get; set; } public DbSet BeatmapSetInfo { get; set; } - public DbSet DatabasedKeyBinding { get; set; } public DbSet DatabasedSetting { get; set; } public DbSet FileInfo { get; set; } public DbSet RulesetInfo { get; set; } public DbSet SkinInfo { get; set; } public DbSet ScoreInfo { get; set; } + // migrated to realm + public DbSet DatabasedKeyBinding { get; set; } + private readonly string connectionString; private static readonly Lazy logger = new Lazy(() => new OsuDbLoggerFactory()); From 8d071f97fb1e193b64c379c82d35374b316f7226 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jan 2021 16:33:03 +0900 Subject: [PATCH 145/520] Early return --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 23 +++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 0f270cbece..17bc913b43 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -163,21 +163,20 @@ namespace osu.Game.Overlays.Toolbar { base.LoadComplete(); - if (Hotkey != null) + if (Hotkey == null) return; + + realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.ActionInt == (int)Hotkey.Value); + + if (realmKeyBinding != null) { - realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.ActionInt == (int)Hotkey.Value); - - if (realmKeyBinding != null) + realmKeyBinding.PropertyChanged += (sender, args) => { - realmKeyBinding.PropertyChanged += (sender, args) => - { - if (args.PropertyName == nameof(realmKeyBinding.KeyCombinationString)) - updateKeyBindingTooltip(); - }; - } - - updateKeyBindingTooltip(); + if (args.PropertyName == nameof(realmKeyBinding.KeyCombinationString)) + updateKeyBindingTooltip(); + }; } + + updateKeyBindingTooltip(); } protected override bool OnMouseDown(MouseDownEvent e) => true; From fd582f521c567c4554b8b60a521189480a882920 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jan 2021 16:33:55 +0900 Subject: [PATCH 146/520] Reduce lifetime of realm context usage in detach scenario --- .../KeyBinding/KeyBindingsSubsection.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index fae42f5492..1cd600a72d 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -37,21 +37,21 @@ namespace osu.Game.Overlays.KeyBinding { var rulesetId = Ruleset?.ID; + List bindings; + using (var realm = realmFactory.GetForRead()) + bindings = realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).Detach(); + + foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { - var bindings = realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).Detach(); + int intKey = (int)defaultGroup.Key; - foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) + // one row per valid action. + Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.ActionInt.Equals(intKey)).ToList()) { - int intKey = (int)defaultGroup.Key; - - // one row per valid action. - Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.ActionInt.Equals(intKey)).ToList()) - { - AllowMainMouseButtons = Ruleset != null, - Defaults = defaultGroup.Select(d => d.KeyCombination) - }); - } + AllowMainMouseButtons = Ruleset != null, + Defaults = defaultGroup.Select(d => d.KeyCombination) + }); } Add(new ResetButton From f26c6210f36c9e73d18cb971ff950823f060900b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jan 2021 16:36:24 +0900 Subject: [PATCH 147/520] Remove unnecessary Take() call and refactor default group logic naming --- osu.Game/Input/RealmKeyBindingStore.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index d55d2362fe..f5c4b646c1 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -62,22 +62,21 @@ namespace osu.Game.Input using (var usage = realmFactory.GetForWrite()) { // compare counts in database vs defaults - foreach (var group in defaults.GroupBy(k => k.Action)) + foreach (var defaultsForAction in defaults.GroupBy(k => k.Action)) { - int count = usage.Realm.All().Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)group.Key); - int aimCount = group.Count(); + int existingCount = usage.Realm.All().Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key); - if (aimCount <= count) + if (defaultsForAction.Count() <= existingCount) continue; - foreach (var insertable in group.Skip(count).Take(aimCount - count)) + foreach (var k in defaultsForAction.Skip(existingCount)) { // insert any defaults which are missing. usage.Realm.Add(new RealmKeyBinding { ID = Guid.NewGuid().ToString(), - KeyCombinationString = insertable.KeyCombination.ToString(), - ActionInt = (int)insertable.Action, + KeyCombinationString = k.KeyCombination.ToString(), + ActionInt = (int)k.Action, RulesetID = rulesetId, Variant = variant }); From 693602513ea5d7b5fbefd8dfb5f2d43b37dbed2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jan 2021 14:22:48 +0900 Subject: [PATCH 148/520] Update test to use GetForWrite --- osu.Game.Tests/Database/TestRealmKeyBindingStore.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 1a7e0d67c5..6c0811f633 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -11,6 +11,7 @@ using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Input; using osu.Game.Input.Bindings; +using Realms; namespace osu.Game.Tests.Database { @@ -62,12 +63,15 @@ namespace osu.Game.Tests.Database Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape })); - var binding = backBinding; + var tsr = ThreadSafeReference.Create(backBinding); - realmContextFactory.Context.Write(() => + using (var usage = realmContextFactory.GetForWrite()) { + var binding = usage.Realm.ResolveReference(tsr); binding.KeyCombination = new KeyCombination(InputKey.BackSpace); - }); + + usage.Commit(); + } Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); From 3e366b1f1545ca0f44e7b28bac1a82985e36b5f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jan 2021 14:26:06 +0900 Subject: [PATCH 149/520] Ensure the main realm context is closed when the factory is disposed --- osu.Game.Tests/Database/TestRealmKeyBindingStore.cs | 1 + osu.Game/Database/RealmContextFactory.cs | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 6c0811f633..cac331451b 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -83,6 +83,7 @@ namespace osu.Game.Tests.Database [TearDown] public void TearDown() { + realmContextFactory.Dispose(); storage.DeleteDirectory(string.Empty); } diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 0631acc750..b76006cd88 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -36,6 +36,9 @@ namespace osu.Game.Database { get { + if (IsDisposed) + throw new InvalidOperationException($"Attempted to access {nameof(Context)} on a disposed context factory"); + if (context == null) { context = createContext(); @@ -92,6 +95,14 @@ namespace osu.Game.Database { } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + context?.Dispose(); + context = null; + } + /// /// A transaction used for making changes to realm data. /// From ddc63662ba9da2635bd4d3b851b1ee8922eea453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 16 Jan 2021 16:39:04 +0100 Subject: [PATCH 150/520] Dispose realm in RealmWriteUsage cleanup --- osu.Game/Database/RealmContextFactory.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index b76006cd88..f735098e88 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -138,6 +138,7 @@ namespace osu.Game.Database { // rollback if not explicitly committed. transaction?.Dispose(); + Realm?.Dispose(); Monitor.Exit(factory.writeLock); pending_writes.Value--; From 0f8f0434f95bce74cf9c0e3ec38e2ad4476b3f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 16 Jan 2021 17:03:58 +0100 Subject: [PATCH 151/520] Remove EF store again after mis-merge Was originally deleted in 536e7229d0cb82504a39f0a18e120da91e0b0f12. --- osu.Game/Input/KeyBindingStore.cs | 108 ------------------------------ 1 file changed, 108 deletions(-) delete mode 100644 osu.Game/Input/KeyBindingStore.cs diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs deleted file mode 100644 index b25b00eb84..0000000000 --- a/osu.Game/Input/KeyBindingStore.cs +++ /dev/null @@ -1,108 +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 System.Collections.Generic; -using System.Linq; -using osu.Framework.Input.Bindings; -using osu.Framework.Platform; -using osu.Game.Database; -using osu.Game.Input.Bindings; -using osu.Game.Rulesets; - -namespace osu.Game.Input -{ - public class KeyBindingStore : DatabaseBackedStore - { - public event Action KeyBindingChanged; - - public KeyBindingStore(DatabaseContextFactory contextFactory, RulesetStore rulesets, Storage storage = null) - : base(contextFactory, storage) - { - using (ContextFactory.GetForWrite()) - { - foreach (var info in rulesets.AvailableRulesets) - { - var ruleset = info.CreateInstance(); - foreach (var variant in ruleset.AvailableVariants) - insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant); - } - } - } - - public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings); - - /// - /// Retrieve all user-defined key combinations (in a format that can be displayed) for a specific action. - /// - /// The action to lookup. - /// A set of display strings for all the user's key configuration for the action. - public IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction) - { - foreach (var action in Query().Where(b => (GlobalAction)b.Action == globalAction)) - { - string str = action.KeyCombination.ReadableString(); - - // even if found, the readable string may be empty for an unbound action. - if (str.Length > 0) - yield return str; - } - } - - private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) - { - using (var usage = ContextFactory.GetForWrite()) - { - // compare counts in database vs defaults - foreach (var group in defaults.GroupBy(k => k.Action)) - { - int count = Query(rulesetId, variant).Count(k => (int)k.Action == (int)group.Key); - int aimCount = group.Count(); - - if (aimCount <= count) - continue; - - foreach (var insertable in group.Skip(count).Take(aimCount - count)) - { - // insert any defaults which are missing. - usage.Context.DatabasedKeyBinding.Add(new DatabasedKeyBinding - { - KeyCombination = insertable.KeyCombination, - Action = insertable.Action, - RulesetID = rulesetId, - Variant = variant - }); - - // required to ensure stable insert order (https://github.com/dotnet/efcore/issues/11686) - usage.Context.SaveChanges(); - } - } - } - } - - /// - /// Retrieve s for a specified ruleset/variant content. - /// - /// The ruleset's internal ID. - /// An optional variant. - /// - public List Query(int? rulesetId = null, int? variant = null) => - ContextFactory.Get().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); - - public void Update(KeyBinding keyBinding) - { - using (ContextFactory.GetForWrite()) - { - var dbKeyBinding = (DatabasedKeyBinding)keyBinding; - Refresh(ref dbKeyBinding); - - if (dbKeyBinding.KeyCombination.Equals(keyBinding.KeyCombination)) - return; - - dbKeyBinding.KeyCombination = keyBinding.KeyCombination; - } - - KeyBindingChanged?.Invoke(); - } - } -} From cd8401c39c591955da4d2be52d4182dea6b6aa3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 16 Jan 2021 17:20:33 +0100 Subject: [PATCH 152/520] Suppress nuget warning due to including beta realm --- Directory.Build.props | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 2e1873a9ed..8e0693f8d5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -33,12 +33,16 @@ DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway. This is required due to https://github.com/NuGet/Home/issues/5740 + NU5104: + This is triggered on osu.Game due to using a beta/prerelease version of realm. + Warning suppression can be removed after migrating off of a beta release. + CA9998: Microsoft.CodeAnalysis.FxCopAnalyzers has been deprecated. The entire package will be able to be removed after migrating to .NET 5, as analysers are shipped as part of the .NET 5 SDK anyway. --> - $(NoWarn);NU1701;CA9998 + $(NoWarn);NU1701;NU5104;CA9998 false From 15db0e97d7f84729034652e2c63e988b51ac15cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jan 2021 18:06:32 +0900 Subject: [PATCH 153/520] Update realm version --- 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 e48c103700..7c49a250c4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + From 68f2e7f61ae35e800fd34e1dff4e1bdf6a411164 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jan 2021 18:22:23 +0900 Subject: [PATCH 154/520] Use realm support for Guid --- osu.Game/Database/IHasGuidPrimaryKey.cs | 12 +----------- osu.Game/Database/RealmContextFactory.cs | 11 ++++++++++- osu.Game/Input/Bindings/RealmKeyBinding.cs | 3 ++- osu.Game/Input/RealmKeyBindingStore.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Database/IHasGuidPrimaryKey.cs b/osu.Game/Database/IHasGuidPrimaryKey.cs index 3b0888c654..ca41d70210 100644 --- a/osu.Game/Database/IHasGuidPrimaryKey.cs +++ b/osu.Game/Database/IHasGuidPrimaryKey.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; using Realms; @@ -11,16 +10,7 @@ namespace osu.Game.Database public interface IHasGuidPrimaryKey { [JsonIgnore] - [Ignored] - public Guid Guid - { - get => new Guid(ID); - set => ID = value.ToString(); - } - - [JsonIgnore] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [PrimaryKey] - string ID { get; set; } + public Guid ID { get; set; } } } diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index f735098e88..918ec1eb53 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -7,7 +7,9 @@ using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; +using osu.Game.Input.Bindings; using Realms; +using Realms.Schema; namespace osu.Game.Database { @@ -17,7 +19,7 @@ namespace osu.Game.Database private const string database_name = @"client"; - private const int schema_version = 5; + private const int schema_version = 6; /// /// Lock object which is held for the duration of a write operation (via ). @@ -93,6 +95,13 @@ namespace osu.Game.Database private void onMigration(Migration migration, ulong lastSchemaVersion) { + switch (lastSchemaVersion) + { + case 5: + // let's keep things simple. changing the type of the primary key is a bit involved. + migration.NewRealm.RemoveAll(); + break; + } } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs index ecffc1fd62..d10cb6af83 100644 --- a/osu.Game/Input/Bindings/RealmKeyBinding.cs +++ b/osu.Game/Input/Bindings/RealmKeyBinding.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; using osu.Framework.Input.Bindings; using osu.Game.Database; using Realms; @@ -11,7 +12,7 @@ namespace osu.Game.Input.Bindings public class RealmKeyBinding : RealmObject, IHasGuidPrimaryKey, IKeyBinding { [PrimaryKey] - public string ID { get; set; } + public Guid ID { get; set; } public int? RulesetID { get; set; } diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index f5c4b646c1..76e65c1c70 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -74,7 +74,7 @@ namespace osu.Game.Input // insert any defaults which are missing. usage.Realm.Add(new RealmKeyBinding { - ID = Guid.NewGuid().ToString(), + ID = Guid.NewGuid(), KeyCombinationString = k.KeyCombination.ToString(), ActionInt = (int)k.Action, RulesetID = rulesetId, diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ae1ec97a4c..d98a20925a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -341,7 +341,7 @@ namespace osu.Game { usage.Realm.Add(new RealmKeyBinding { - ID = Guid.NewGuid().ToString(), + ID = Guid.NewGuid(), KeyCombinationString = dkb.KeyCombination.ToString(), ActionInt = (int)dkb.Action, RulesetID = dkb.RulesetID, From f6c20095094bdbf26c6ffe55dfdab12fe2f7e083 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jan 2021 20:10:10 +0900 Subject: [PATCH 155/520] Remove unused using --- osu.Game/Database/RealmContextFactory.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 918ec1eb53..d7e35f736e 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -9,7 +9,6 @@ using osu.Framework.Platform; using osu.Framework.Statistics; using osu.Game.Input.Bindings; using Realms; -using Realms.Schema; namespace osu.Game.Database { From d2bf3a58057f1ea5917e16bdc2288e7e068282ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jan 2021 19:01:58 +0900 Subject: [PATCH 156/520] Add ignore files to avoid copying realm management/pipes --- osu.Game/IO/OsuStorage.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 8097f61ea4..f7abd2a6f1 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -33,12 +33,17 @@ namespace osu.Game.IO private readonly StorageConfigManager storageConfig; private readonly Storage defaultStorage; - public override string[] IgnoreDirectories => new[] { "cache" }; + public override string[] IgnoreDirectories => new[] + { + "cache", + "client.realm.management" + }; public override string[] IgnoreFiles => new[] { "framework.ini", - "storage.ini" + "storage.ini", + "client.realm.note" }; public OsuStorage(GameHost host, Storage defaultStorage) From 34a7ce912eaf46cdf691d1525a8f4dfa105d4476 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jan 2021 19:02:09 +0900 Subject: [PATCH 157/520] Correctly close context before attempting migration --- osu.Game/Database/RealmContextFactory.cs | 17 +++++++++++------ osu.Game/OsuGameBase.cs | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index d7e35f736e..35ff91adb2 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -1,6 +1,3 @@ -// 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.Threading; using osu.Framework.Graphics; @@ -77,7 +74,7 @@ namespace osu.Game.Database { base.Update(); - if (Context.Refresh()) + if (context?.Refresh() == true) refreshes.Value++; } @@ -107,8 +104,7 @@ namespace osu.Game.Database { base.Dispose(isDisposing); - context?.Dispose(); - context = null; + FlushConnections(); } /// @@ -152,5 +148,14 @@ namespace osu.Game.Database pending_writes.Value--; } } + + public void FlushConnections() + { + var previousContext = context; + context = null; + previousContext?.Dispose(); + while (previousContext?.IsClosed == false) + Thread.Sleep(50); + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 759be79045..4bd4a6ae7f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -513,6 +513,7 @@ namespace osu.Game public void Migrate(string path) { contextFactory.FlushConnections(); + realmFactory.FlushConnections(); (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); } } From 47a9d2b1c2c49fe9c8bdb9024c00857786469734 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jan 2021 20:53:16 +0900 Subject: [PATCH 158/520] Add missing licence header --- osu.Game/Database/RealmContextFactory.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 35ff91adb2..0ff4f857b9 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -1,3 +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 System; using System.Threading; using osu.Framework.Graphics; From d480aa0e42187b35f819b7e6ad9b44baf1bf6579 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jan 2021 22:57:55 +0900 Subject: [PATCH 159/520] Don't check for all ignored files being present in original folder (the realm exception is platform dependent) --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 045246e5ed..b0f9768e09 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -142,7 +142,8 @@ namespace osu.Game.Tests.NonVisual foreach (var file in osuStorage.IgnoreFiles) { - Assert.That(File.Exists(Path.Combine(originalDirectory, file))); + if (file.EndsWith(".ini", StringComparison.Ordinal)) + Assert.That(File.Exists(Path.Combine(originalDirectory, file))); Assert.That(storage.Exists(file), Is.False); } From d69a4914e05f5f5d1c2802fcad8551f9c1fe52c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jan 2021 17:28:47 +0900 Subject: [PATCH 160/520] Add method to block all realm access during migration operation --- osu.Game/Database/IRealmFactory.cs | 2 +- osu.Game/Database/RealmContextFactory.cs | 87 ++++++++++++++----- osu.Game/OsuGameBase.cs | 8 +- .../KeyBinding/KeyBindingsSubsection.cs | 4 +- 4 files changed, 73 insertions(+), 28 deletions(-) diff --git a/osu.Game/Database/IRealmFactory.cs b/osu.Game/Database/IRealmFactory.cs index 4a92a5683b..025c44f440 100644 --- a/osu.Game/Database/IRealmFactory.cs +++ b/osu.Game/Database/IRealmFactory.cs @@ -15,7 +15,7 @@ namespace osu.Game.Database /// /// Get a fresh context for read usage. /// - Realm GetForRead(); + RealmContextFactory.RealmUsage GetForRead(); /// /// Request a context for write usage. diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 0ff4f857b9..c5d0061143 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -3,6 +3,7 @@ using System; using System.Threading; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Platform; @@ -30,6 +31,9 @@ namespace osu.Game.Database private static readonly GlobalStatistic refreshes = GlobalStatistics.Get("Realm", "Dirty Refreshes"); private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get("Realm", "Contexts (Created)"); private static readonly GlobalStatistic pending_writes = GlobalStatistics.Get("Realm", "Pending writes"); + private static readonly GlobalStatistic active_usages = GlobalStatistics.Get("Realm", "Active usages"); + + private readonly ManualResetEventSlim blockingResetEvent = new ManualResetEventSlim(true); private Realm context; @@ -57,10 +61,10 @@ namespace osu.Game.Database this.storage = storage; } - public Realm GetForRead() + public RealmUsage GetForRead() { reads.Value++; - return createContext(); + return new RealmUsage(this); } public RealmWriteUsage GetForWrite() @@ -83,6 +87,8 @@ namespace osu.Game.Database private Realm createContext() { + blockingResetEvent.Wait(); + contexts_created.Value++; return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) @@ -107,24 +113,69 @@ namespace osu.Game.Database { base.Dispose(isDisposing); - FlushConnections(); + BlockAllOperations(); + } + + public IDisposable BlockAllOperations() + { + blockingResetEvent.Reset(); + flushContexts(); + + return new InvokeOnDisposal(this, r => endBlockingSection()); + } + + private void endBlockingSection() + { + blockingResetEvent.Set(); + } + + private void flushContexts() + { + var previousContext = context; + context = null; + + // wait for all threaded usages to finish + while (active_usages.Value > 0) + Thread.Sleep(50); + + previousContext?.Dispose(); + } + + /// + /// A usage of realm from an arbitrary thread. + /// + public class RealmUsage : IDisposable + { + public readonly Realm Realm; + + protected readonly RealmContextFactory Factory; + + internal RealmUsage(RealmContextFactory factory) + { + Factory = factory; + Realm = factory.createContext(); + } + + /// + /// Disposes this instance, calling the initially captured action. + /// + public virtual void Dispose() + { + Realm?.Dispose(); + active_usages.Value--; + } } /// /// A transaction used for making changes to realm data. /// - public class RealmWriteUsage : IDisposable + public class RealmWriteUsage : RealmUsage { - public readonly Realm Realm; - - private readonly RealmContextFactory factory; private readonly Transaction transaction; internal RealmWriteUsage(RealmContextFactory factory) + : base(factory) { - this.factory = factory; - - Realm = factory.createContext(); transaction = Realm.BeginWrite(); } @@ -141,24 +192,16 @@ namespace osu.Game.Database /// /// Disposes this instance, calling the initially captured action. /// - public virtual void Dispose() + public override void Dispose() { // rollback if not explicitly committed. transaction?.Dispose(); - Realm?.Dispose(); - Monitor.Exit(factory.writeLock); + base.Dispose(); + + Monitor.Exit(Factory.writeLock); pending_writes.Value--; } } - - public void FlushConnections() - { - var previousContext = context; - context = null; - previousContext?.Dispose(); - while (previousContext?.IsClosed == false) - Thread.Sleep(50); - } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4bd4a6ae7f..7806a38cd9 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -512,9 +512,11 @@ namespace osu.Game public void Migrate(string path) { - contextFactory.FlushConnections(); - realmFactory.FlushConnections(); - (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); + using (realmFactory.BlockAllOperations()) + { + contextFactory.FlushConnections(); + (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); + } } } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index 1cd600a72d..ac77440dfa 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -39,8 +39,8 @@ namespace osu.Game.Overlays.KeyBinding List bindings; - using (var realm = realmFactory.GetForRead()) - bindings = realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).Detach(); + using (var usage = realmFactory.GetForRead()) + bindings = usage.Realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).Detach(); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { From 4d976094d1c69613ef581828d638ffdb04a48984 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Mar 2021 20:07:53 +0900 Subject: [PATCH 161/520] Switch Guid implementation temporarily to avoid compile time error --- osu.Game/Input/Bindings/RealmKeyBinding.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs index d10cb6af83..9abb749328 100644 --- a/osu.Game/Input/Bindings/RealmKeyBinding.cs +++ b/osu.Game/Input/Bindings/RealmKeyBinding.cs @@ -12,7 +12,14 @@ namespace osu.Game.Input.Bindings public class RealmKeyBinding : RealmObject, IHasGuidPrimaryKey, IKeyBinding { [PrimaryKey] - public Guid ID { get; set; } + public string StringGuid { get; set; } + + [Ignored] + public Guid ID + { + get => Guid.Parse(StringGuid); + set => StringGuid = value.ToString(); + } public int? RulesetID { get; set; } From 015cf5f7eba34442e07fd888af802443e3999075 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 13:22:48 +0900 Subject: [PATCH 162/520] Fix tests using wrong ID lookup type --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 0f9505beda..87a6fe00cc 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -131,7 +131,7 @@ namespace osu.Game.Overlays.KeyBinding using (var usage = realmFactory.GetForWrite()) { - var binding = usage.Realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); + var binding = usage.Realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID.ToString()); binding.KeyCombinationString = button.KeyBinding.KeyCombinationString; usage.Commit(); @@ -296,7 +296,7 @@ namespace osu.Game.Overlays.KeyBinding { using (var write = realmFactory.GetForWrite()) { - var binding = write.Realm.Find(((IHasGuidPrimaryKey)bindTarget.KeyBinding).ID); + var binding = write.Realm.Find(((IHasGuidPrimaryKey)bindTarget.KeyBinding).ID.ToString()); binding.KeyCombinationString = bindTarget.KeyBinding.KeyCombinationString; write.Commit(); From 1281273dd32d7fb47ad4e943a46d7d8099b8b7e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 22:46:20 +0900 Subject: [PATCH 163/520] Add back automapper dependency --- osu.Game/osu.Game.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7d4ae6ff8f..97ae793f7f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,6 +18,7 @@ + From 37bf79e8a412d88b4796627b4447d78458bd56af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Mar 2021 15:10:03 +0900 Subject: [PATCH 164/520] Remove unused automapper setup for the time being --- osu.Game/Database/RealmExtensions.cs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs index 04b0820dd9..aee36e81c5 100644 --- a/osu.Game/Database/RealmExtensions.cs +++ b/osu.Game/Database/RealmExtensions.cs @@ -3,13 +3,7 @@ using System.Collections.Generic; using AutoMapper; -using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Input.Bindings; -using osu.Game.IO; -using osu.Game.Rulesets; -using osu.Game.Scoring; -using osu.Game.Skinning; using Realms; namespace osu.Game.Database @@ -21,22 +15,7 @@ namespace osu.Game.Database c.ShouldMapField = fi => false; c.ShouldMapProperty = pi => pi.SetMethod != null && pi.SetMethod.IsPublic; - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - - c.CreateMap() - .ForMember(s => s.Beatmaps, d => d.MapFrom(s => s.Beatmaps)) - .ForMember(s => s.Files, d => d.MapFrom(s => s.Files)) - .MaxDepth(2); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); }).CreateMapper(); /// From ecde6137e0f907644eb426b37268de6e07d7cf31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Mar 2021 15:16:01 +0900 Subject: [PATCH 165/520] Add missing active usage counter increment --- osu.Game/Database/RealmContextFactory.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index c5d0061143..ed5931dd2b 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -152,6 +152,7 @@ namespace osu.Game.Database internal RealmUsage(RealmContextFactory factory) { + active_usages.Value++; Factory = factory; Realm = factory.createContext(); } From 7e73af1c5a1a6a95b7d54c208e0de84b612015bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 31 Mar 2021 23:39:23 +0200 Subject: [PATCH 166/520] Revert "Suppress nuget warning due to including beta realm" This reverts commit cd8401c39c591955da4d2be52d4182dea6b6aa3a. Suppression is no longer necessary, as a normal realm release is used now. --- Directory.Build.props | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index a91d423043..53ad973e47 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -33,16 +33,12 @@ DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway. This is required due to https://github.com/NuGet/Home/issues/5740 - NU5104: - This is triggered on osu.Game due to using a beta/prerelease version of realm. - Warning suppression can be removed after migrating off of a beta release. - CA9998: Microsoft.CodeAnalysis.FxCopAnalyzers has been deprecated. The entire package will be able to be removed after migrating to .NET 5, as analysers are shipped as part of the .NET 5 SDK anyway. --> - $(NoWarn);NU1701;NU5104;CA9998 + $(NoWarn);NU1701;CA9998 false From cc9db90d11f6f6d3f0882d194efeaaaa4e696484 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 3 Apr 2021 18:58:25 +0900 Subject: [PATCH 167/520] Extract common implementation into private method --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 87a6fe00cc..767852896b 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -129,13 +129,7 @@ namespace osu.Game.Overlays.KeyBinding var button = buttons[i++]; button.UpdateKeyCombination(d); - using (var usage = realmFactory.GetForWrite()) - { - var binding = usage.Realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID.ToString()); - binding.KeyCombinationString = button.KeyBinding.KeyCombinationString; - - usage.Commit(); - } + updateStoreFromButton(button); } } @@ -294,13 +288,7 @@ namespace osu.Game.Overlays.KeyBinding { if (bindTarget != null) { - using (var write = realmFactory.GetForWrite()) - { - var binding = write.Realm.Find(((IHasGuidPrimaryKey)bindTarget.KeyBinding).ID.ToString()); - binding.KeyCombinationString = bindTarget.KeyBinding.KeyCombinationString; - - write.Commit(); - } + updateStoreFromButton(bindTarget); bindTarget.IsBinding = false; Schedule(() => @@ -345,6 +333,17 @@ namespace osu.Game.Overlays.KeyBinding if (bindTarget != null) bindTarget.IsBinding = true; } + private void updateStoreFromButton(KeyButton button) + { + using (var usage = realmFactory.GetForWrite()) + { + var binding = usage.Realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID.ToString()); + binding.KeyCombinationString = button.KeyBinding.KeyCombinationString; + + usage.Commit(); + } + } + private class CancelButton : TriangleButton { public CancelButton() From f9603eefe5fed7e02c040a0e12a4b27541e4aa60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Apr 2021 01:59:55 +0900 Subject: [PATCH 168/520] Revert "Switch Guid implementation temporarily to avoid compile time error" This reverts commit 4d976094d1c69613ef581828d638ffdb04a48984. --- osu.Game/Input/Bindings/RealmKeyBinding.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs index 9abb749328..d10cb6af83 100644 --- a/osu.Game/Input/Bindings/RealmKeyBinding.cs +++ b/osu.Game/Input/Bindings/RealmKeyBinding.cs @@ -12,14 +12,7 @@ namespace osu.Game.Input.Bindings public class RealmKeyBinding : RealmObject, IHasGuidPrimaryKey, IKeyBinding { [PrimaryKey] - public string StringGuid { get; set; } - - [Ignored] - public Guid ID - { - get => Guid.Parse(StringGuid); - set => StringGuid = value.ToString(); - } + public Guid ID { get; set; } public int? RulesetID { get; set; } From 0fce0a420463f80b111cb9f976db6fdee7d1c3ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Apr 2021 02:04:42 +0900 Subject: [PATCH 169/520] Update to prerelease realm version --- osu.Game/FodyWeavers.xsd | 8 +++++++- osu.Game/osu.Game.csproj | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/FodyWeavers.xsd b/osu.Game/FodyWeavers.xsd index f526bddb09..447878c551 100644 --- a/osu.Game/FodyWeavers.xsd +++ b/osu.Game/FodyWeavers.xsd @@ -5,7 +5,13 @@ - + + + + Disables anonymized usage information from being sent on build. Read more about what data is being collected and why here: https://github.com/realm/realm-dotnet/blob/master/Realm/Realm.Fody/Common/Analytics.cs + + + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3bc0874b2d..81b89d587c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -32,7 +32,7 @@ - + From 6dd48f204c2dec42e6afb837bab65b06a15eb39e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Apr 2021 02:05:53 +0900 Subject: [PATCH 170/520] Remove unused store resolution --- osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 98b86d0b82..5f500d3023 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -26,9 +26,6 @@ namespace osu.Game.Input.Bindings private IDisposable realmSubscription; private IQueryable realmKeyBindings; - [Resolved] - private RealmKeyBindingStore store { get; set; } - [Resolved] private RealmContextFactory realmFactory { get; set; } From b9ee63ff891da7ce69245fc3f3bf66177064ef72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Apr 2021 02:13:29 +0900 Subject: [PATCH 171/520] Remove `public` keywords from interface implementations --- osu.Game/Database/IHasGuidPrimaryKey.cs | 2 +- osu.Game/Database/IRealmFactory.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/IHasGuidPrimaryKey.cs b/osu.Game/Database/IHasGuidPrimaryKey.cs index ca41d70210..c9cd9b257a 100644 --- a/osu.Game/Database/IHasGuidPrimaryKey.cs +++ b/osu.Game/Database/IHasGuidPrimaryKey.cs @@ -11,6 +11,6 @@ namespace osu.Game.Database { [JsonIgnore] [PrimaryKey] - public Guid ID { get; set; } + Guid ID { get; set; } } } diff --git a/osu.Game/Database/IRealmFactory.cs b/osu.Game/Database/IRealmFactory.cs index 025c44f440..c79442134c 100644 --- a/osu.Game/Database/IRealmFactory.cs +++ b/osu.Game/Database/IRealmFactory.cs @@ -10,7 +10,7 @@ namespace osu.Game.Database /// /// The main realm context, bound to the update thread. /// - public Realm Context { get; } + Realm Context { get; } /// /// Get a fresh context for read usage. From 311cfe04bbc8842f98ad08c00a46dbf63fbfde2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 16 Jan 2021 17:20:33 +0100 Subject: [PATCH 172/520] Suppress nuget warning due to including beta realm --- Directory.Build.props | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 53ad973e47..a91d423043 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -33,12 +33,16 @@ DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway. This is required due to https://github.com/NuGet/Home/issues/5740 + NU5104: + This is triggered on osu.Game due to using a beta/prerelease version of realm. + Warning suppression can be removed after migrating off of a beta release. + CA9998: Microsoft.CodeAnalysis.FxCopAnalyzers has been deprecated. The entire package will be able to be removed after migrating to .NET 5, as analysers are shipped as part of the .NET 5 SDK anyway. --> - $(NoWarn);NU1701;CA9998 + $(NoWarn);NU1701;NU5104;CA9998 false From 3bf462e4fad1855a538417171d7b1ffdd17dff7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:35:26 +0900 Subject: [PATCH 173/520] Add ignore rule for migrations for client.realm.lock --- osu.Game/IO/OsuStorage.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 1ed9a80a80..75130b0f9b 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -43,7 +43,8 @@ namespace osu.Game.IO { "framework.ini", "storage.ini", - "client.realm.note" + "client.realm.note", + "client.realm.lock", }; public OsuStorage(GameHost host, Storage defaultStorage) From 2c1422b4f90fd1abaa3552cd19730491d4b6b6b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:37:19 +0900 Subject: [PATCH 174/520] Add comment regarding teste edge case --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 6796344f24..a540ad7247 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -142,6 +142,8 @@ namespace osu.Game.Tests.NonVisual foreach (var file in osuStorage.IgnoreFiles) { + // avoid touching realm files which may be a pipe and break everything. + // this is also done locally inside OsuStorage via the IgnoreFiles list. if (file.EndsWith(".ini", StringComparison.Ordinal)) Assert.That(File.Exists(Path.Combine(originalDirectory, file))); Assert.That(storage.Exists(file), Is.False); From 8961203f0858b883783966a9b35ac6ce405d4cf9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 17:06:03 +0900 Subject: [PATCH 175/520] Move guid initialisation to database model itself --- osu.Game/Input/Bindings/RealmKeyBinding.cs | 2 +- osu.Game/Input/RealmKeyBindingStore.cs | 1 - osu.Game/OsuGameBase.cs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs index d10cb6af83..334d2da427 100644 --- a/osu.Game/Input/Bindings/RealmKeyBinding.cs +++ b/osu.Game/Input/Bindings/RealmKeyBinding.cs @@ -12,7 +12,7 @@ namespace osu.Game.Input.Bindings public class RealmKeyBinding : RealmObject, IHasGuidPrimaryKey, IKeyBinding { [PrimaryKey] - public Guid ID { get; set; } + public Guid ID { get; set; } = Guid.NewGuid(); public int? RulesetID { get; set; } diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 76e65c1c70..ca830286df 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -74,7 +74,6 @@ namespace osu.Game.Input // insert any defaults which are missing. usage.Realm.Add(new RealmKeyBinding { - ID = Guid.NewGuid(), KeyCombinationString = k.KeyCombination.ToString(), ActionInt = (int)k.Action, RulesetID = rulesetId, diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0bb4d57ec3..c0796f41a0 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -370,7 +370,6 @@ namespace osu.Game { usage.Realm.Add(new RealmKeyBinding { - ID = Guid.NewGuid(), KeyCombinationString = dkb.KeyCombination.ToString(), ActionInt = (int)dkb.Action, RulesetID = dkb.RulesetID, From 61f3cc9bc2cc843f50e6b1548a3e14929619e494 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 17:10:28 +0900 Subject: [PATCH 176/520] Fix update method not switched across to using `Guid` --- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 767852896b..d98fea8eec 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -337,7 +337,7 @@ namespace osu.Game.Overlays.KeyBinding { using (var usage = realmFactory.GetForWrite()) { - var binding = usage.Realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID.ToString()); + var binding = usage.Realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); binding.KeyCombinationString = button.KeyBinding.KeyCombinationString; usage.Commit(); From 253c66034d05011838b1a8bfc994e49517a9fbd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 17:45:43 +0900 Subject: [PATCH 177/520] Remove unused using statement --- osu.Game/Input/RealmKeyBindingStore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index ca830286df..547c79c209 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.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 System; using System.Collections.Generic; using System.Linq; using osu.Framework.Input.Bindings; From 21b6adbf791a213c581aafddcd12a3da3d2ac19f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 17:52:17 +0900 Subject: [PATCH 178/520] Remove DI caching of `RealmKeyBindingStore` --- osu.Game/OsuGameBase.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c0796f41a0..1e0c8f1332 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -290,8 +290,6 @@ namespace osu.Game migrateDataToRealm(); - dependencies.CacheAs(KeyBindingStore = new RealmKeyBindingStore(realmFactory)); - dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); From 9770c316e2bab93008785cfb880301f99e7a323c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 18:24:28 +0900 Subject: [PATCH 179/520] Add back the construction of the `KeyBindingStore` This reverts commit 21b6adbf791a213c581aafddcd12a3da3d2ac19f. --- osu.Game/OsuGameBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 1e0c8f1332..4c5920c616 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -337,6 +337,7 @@ namespace osu.Game base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); + KeyBindingStore = new RealmKeyBindingStore(realmFactory); KeyBindingStore.Register(globalBindings); foreach (var r in RulesetStore.AvailableRulesets) From 5f6fd9ba13a81a4f308a19ab3021e2bdaa525b26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 19:07:42 +0900 Subject: [PATCH 180/520] Remove outdated dependency requirement in test --- osu.Game.Tests/Visual/TestSceneOsuGame.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs index bcad8f2d3c..4e5e8517a4 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs @@ -74,7 +74,6 @@ namespace osu.Game.Tests.Visual typeof(FileStore), typeof(ScoreManager), typeof(BeatmapManager), - typeof(RealmKeyBindingStore), typeof(SettingsStore), typeof(RulesetConfigCache), typeof(OsuColour), From 2a87b3d74bdf0bbecaea497d6992fb6760b667d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 13:43:55 +0900 Subject: [PATCH 181/520] Update realm package to latest beta version --- 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 c2a3f997ce..6af7d82fdb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 9563b73ea645615630ab87daccfc806432f3af9f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 14:13:58 +0900 Subject: [PATCH 182/520] Remove unnecessary using statement --- osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index d6ca839485..00180eae60 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Game.Database; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays.Settings; From a7f50c5da66be62db20718ed540c5c7821735937 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 17:13:25 +0900 Subject: [PATCH 183/520] Revert "Update realm package to latest beta version" This reverts commit 2a87b3d74bdf0bbecaea497d6992fb6760b667d5. --- 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 6af7d82fdb..c2a3f997ce 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 88bdd8a7b767552c16f676e64f25585aecb41ff6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 16:01:20 +0900 Subject: [PATCH 184/520] Update some out of date code pieces --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- .../Settings/Sections/Online/AlertsAndPrivacySettings.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index e74ae1aeee..1c92c16333 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -61,8 +61,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ShowOnlineExplicitContent, false); - Set(OsuSetting.ChatHighlightName, true); - Set(OsuSetting.ChatMessageNotification, true); + SetDefault(OsuSetting.ChatHighlightName, true); + SetDefault(OsuSetting.ChatMessageNotification, true); // Audio SetDefault(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); diff --git a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs index 0898ce3b84..f9f5b927b7 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs @@ -19,12 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Online new SettingsCheckbox { LabelText = "Show a notification popup when someone says your name", - Bindable = config.GetBindable(OsuSetting.ChatHighlightName) + Current = config.GetBindable(OsuSetting.ChatHighlightName) }, new SettingsCheckbox { LabelText = "Show private message notifications", - Bindable = config.GetBindable(OsuSetting.ChatMessageNotification) + Current = config.GetBindable(OsuSetting.ChatMessageNotification) }, }; } From d47370bac93ad869c7505c58bfdf003b133c2745 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 27 May 2021 00:59:29 +0200 Subject: [PATCH 185/520] Locally bind to LocalUser --- osu.Game/Online/Chat/MessageNotifier.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 05ffcb03a2..a8ade8e771 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -35,7 +35,7 @@ namespace osu.Game.Online.Chat private Bindable notifyOnMention; private Bindable notifyOnPM; - private IBindable localUser; + private IBindable localUser = new Bindable(); private readonly BindableList joinedChannels = new BindableList(); [BackgroundDependencyLoader] @@ -43,9 +43,9 @@ namespace osu.Game.Online.Chat { notifyOnMention = config.GetBindable(OsuSetting.ChatHighlightName); notifyOnPM = config.GetBindable(OsuSetting.ChatMessageNotification); - localUser = api.LocalUser; channelManager.JoinedChannels.BindTo(joinedChannels); + api.LocalUser.BindTo(localUser); // Listen for new messages joinedChannels.CollectionChanged += channelsChanged; From cf39e58ce733fdd2288bd88b8157fe3a74decef5 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 27 May 2021 01:00:08 +0200 Subject: [PATCH 186/520] Subscribe to CollectionChanged before binding to JoinedChannels --- osu.Game/Online/Chat/MessageNotifier.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index a8ade8e771..1f3fc0946b 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -43,12 +43,12 @@ namespace osu.Game.Online.Chat { notifyOnMention = config.GetBindable(OsuSetting.ChatHighlightName); notifyOnPM = config.GetBindable(OsuSetting.ChatMessageNotification); - - channelManager.JoinedChannels.BindTo(joinedChannels); api.LocalUser.BindTo(localUser); // Listen for new messages joinedChannels.CollectionChanged += channelsChanged; + + channelManager.JoinedChannels.BindTo(joinedChannels); } private void channelsChanged(object sender, NotifyCollectionChangedEventArgs e) From a679efac1c3b85e036965ca6cf7a8ba2558f9b77 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 27 May 2021 01:00:26 +0200 Subject: [PATCH 187/520] Reduce duplicate notification code by making a base class --- osu.Game/Online/Chat/MessageNotifier.cs | 39 +++++++++---------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 1f3fc0946b..47758673bb 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -142,12 +142,10 @@ namespace osu.Game.Online.Chat /// If the mentions the private static bool isMentioning(string message, string username) => message.IndexOf(username, StringComparison.OrdinalIgnoreCase) != -1 || message.IndexOf(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase) != -1; - public class PrivateMessageNotification : SimpleNotification + public class OpenChannelNotification : SimpleNotification { - public PrivateMessageNotification(string username, Channel channel) + public OpenChannelNotification(Channel channel) { - Icon = FontAwesome.Solid.Envelope; - Text = $"You received a private message from '{username}'. Click to read it!"; this.channel = channel; } @@ -171,32 +169,21 @@ namespace osu.Game.Online.Chat } } - public class MentionNotification : SimpleNotification + public class PrivateMessageNotification : OpenChannelNotification { - public MentionNotification(string username, Channel channel) + public PrivateMessageNotification(string username, Channel channel) : base(channel) + { + Icon = FontAwesome.Solid.Envelope; + Text = $"You received a private message from '{username}'. Click to read it!"; + } + } + + public class MentionNotification : OpenChannelNotification + { + public MentionNotification(string username, Channel channel) : base(channel) { Icon = FontAwesome.Solid.At; Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; - this.channel = channel; - } - - private readonly Channel channel; - - public override bool IsImportant => false; - - [BackgroundDependencyLoader] - private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager) - { - IconBackgound.Colour = colours.PurpleDark; - - Activated = delegate - { - notificationOverlay.Hide(); - chatOverlay.Show(); - channelManager.CurrentChannel.Value = channel; - - return true; - }; } } } From 2166ab87c6a5a6d8d19c9b9c24eef6da8eebd0cf Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 27 May 2021 01:47:00 +0200 Subject: [PATCH 188/520] Change base type of tests Fixes missing API property --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 5e0a3994e1..26b0063178 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -16,7 +16,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Online { - public class TestSceneMessageNotifier : ManualInputManagerTestScene + public class TestSceneMessageNotifier : OsuManualInputManagerTestScene { private User friend; private Channel publicChannel; From 0b17af81f186c2f248307d185fb760ec2688e2bf Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 27 May 2021 09:48:30 +0000 Subject: [PATCH 189/520] Use Contains instead of IndexOf Co-authored-by: Berkan Diler --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 47758673bb..5b3293f7ee 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -140,7 +140,7 @@ namespace osu.Game.Online.Chat /// Checks if contains , if not, retries making spaces into underscores. /// /// If the mentions the - private static bool isMentioning(string message, string username) => message.IndexOf(username, StringComparison.OrdinalIgnoreCase) != -1 || message.IndexOf(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase) != -1; + private static bool isMentioning(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase); public class OpenChannelNotification : SimpleNotification { From 13b2b7c14893d2e150085c32eacc27edbf0f3262 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 27 May 2021 21:58:54 +0200 Subject: [PATCH 190/520] Fix formatting --- osu.Game/Online/Chat/MessageNotifier.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 47758673bb..2e4dc7b0aa 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -35,7 +35,7 @@ namespace osu.Game.Online.Chat private Bindable notifyOnMention; private Bindable notifyOnPM; - private IBindable localUser = new Bindable(); + private readonly IBindable localUser = new Bindable(); private readonly BindableList joinedChannels = new BindableList(); [BackgroundDependencyLoader] @@ -171,7 +171,8 @@ namespace osu.Game.Online.Chat public class PrivateMessageNotification : OpenChannelNotification { - public PrivateMessageNotification(string username, Channel channel) : base(channel) + public PrivateMessageNotification(string username, Channel channel) + : base(channel) { Icon = FontAwesome.Solid.Envelope; Text = $"You received a private message from '{username}'. Click to read it!"; @@ -180,7 +181,8 @@ namespace osu.Game.Online.Chat public class MentionNotification : OpenChannelNotification { - public MentionNotification(string username, Channel channel) : base(channel) + public MentionNotification(string username, Channel channel) + : base(channel) { Icon = FontAwesome.Solid.At; Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; From 9ad87ee5dc421eb1208d07b1d4b5356a130a383c Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 27 May 2021 15:04:22 +0900 Subject: [PATCH 191/520] add sfx for results screen + sound design tool --- .../Visual/Ranking/TestSceneAccuracyCircle.cs | 2 +- .../SoundDesign/TestSceneAccuracyCircle.cs | 969 ++++++++++++++++++ .../Expanded/Accuracy/AccuracyCircle.cs | 374 ++++++- .../Expanded/ExpandedPanelMiddleContent.cs | 2 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 7 - 5 files changed, 1343 insertions(+), 11 deletions(-) create mode 100644 osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index f305b7255e..a5e2f02f31 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Ranking } } }, - new AccuracyCircle(score) + new AccuracyCircle(score, true) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs new file mode 100644 index 0000000000..c7ff7f9760 --- /dev/null +++ b/osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs @@ -0,0 +1,969 @@ +// 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.IO; +using Newtonsoft.Json; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Expanded.Accuracy; +using osu.Game.Tests.Beatmaps; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tests.Visual.SoundDesign +{ + [Serializable] + public class TestSceneAccuracyCircle : OsuTestScene + { + [Resolved] + private AudioManager audioManager { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + private DrawableSample previewSampleChannel; + private AccuracyCircleAudioSettings settings = new AccuracyCircleAudioSettings(); + private OsuTextBox saveFilename; + + private Storage presetStorage; + private FileSelector presetFileSelector; + + private Bindable sampleLoadTarget = new Bindable(); + private Bindable selectedSampleName = new Bindable(); + + private Container accuracyCircle; + + private enum SampleLoadTarget + { + ScoreTick, + BadgeDink, + BadgeDinkMax, + Swoosh, + ImpactD, + ImpactC, + ImpactB, + ImpactA, + ImpactS, + ImpactSS, + ApplauseD, + ApplauseC, + ApplauseB, + ApplauseA, + ApplauseS, + ApplauseSS, + }; + + private enum SectionTabs + { + [System.ComponentModel.Description("Score Ticks")] + ScoreTicks, + + [System.ComponentModel.Description("Badge Dinks")] + BadgeDinks, + + [System.ComponentModel.Description("Swoosh")] + Swoosh, + + [System.ComponentModel.Description("Impact")] + Impact, + + [System.ComponentModel.Description("Applause")] + Applause, + + [System.ComponentModel.Description("Preset")] + Preset + } + + private OsuTabControl tabSelector; + + private Dictionary tabContainers = new Dictionary(); + private FillFlowContainer sampleSelectContainer; + + private FileSelector sampleFileSelector; + + [BackgroundDependencyLoader] + private void load(GameHost host) + { + presetStorage = host.Storage.GetStorageForDirectory("presets"); + + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("222") + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Padding = new MarginPadding(10), + Children = new Drawable[] + { + tabSelector = new OsuTabControl + { + RelativeSizeAxes = Axes.X, + Width = 1f, + Height = 24, + }, + + #region score ticks + + // ==================== SCORE TICKS ==================== + tabContainers[SectionTabs.ScoreTicks] = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Width = 1f, + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Play Ticks", + Current = { BindTarget = settings.PlayTicks } + }, + new SettingsSlider + { + LabelText = "Tick Volume (Start)", + Current = { BindTarget = settings.TickVolumeStart } + }, + new SettingsSlider + { + LabelText = "Tick Volume (End)", + Current = { BindTarget = settings.TickVolumeEnd } + }, + new SettingsSlider + { + LabelText = "ScoreTick Start Debounce Rate", + Current = { BindTarget = settings.TickDebounceStart } + }, + new SettingsSlider + { + LabelText = "ScoreTick End Debounce Rate", + Current = { BindTarget = settings.TickDebounceEnd } + }, + new OsuSpriteText + { + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + Text = "ScoreTick Rate Easing:" + }, + new SettingsEnumDropdown + { + Current = { BindTarget = settings.TickRateEasing } + }, + new SettingsSlider + { + LabelText = "ScoreTick Pitch Factor", + Current = { BindTarget = settings.TickPitchFactor } + }, + new OsuSpriteText + { + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + Text = "Pitch Easing:" + }, + new SettingsEnumDropdown + { + Current = { BindTarget = settings.TickPitchEasing } + }, + new OsuSpriteText + { + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + Text = "Volume Easing:" + }, + new SettingsEnumDropdown + { + Current = { BindTarget = settings.TickVolumeEasing } + }, + new OsuSpriteText + { + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + Text = "Tick Sample:" + }, + new OsuSpriteText + { + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2 }, + Current = { BindTarget = settings.TickSampleName } + } + } + }, + + #endregion + + #region badge dinks + + // ==================== BADGE DINKS ==================== + tabContainers[SectionTabs.BadgeDinks] = new FillFlowContainer + { + Alpha = 0, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Width = 1f, + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Play BadgeSounds", + Current = { BindTarget = settings.PlayBadgeSounds } + }, + new SettingsSlider + { + LabelText = "Badge Dink Volume", + Current = { BindTarget = settings.BadgeDinkVolume } + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Badge Dink Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.BadgeSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Badge Max Dink Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.BadgeMaxSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + } + } + }, + + #endregion + + #region swoosh + + // ==================== SWOOSHES ==================== + tabContainers[SectionTabs.Swoosh] = new FillFlowContainer + { + Alpha = 0, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Width = 1f, + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Play Swoosh", + Current = { BindTarget = settings.PlaySwooshSound } + }, + new SettingsSlider + { + LabelText = "Swoosh Volume", + Current = { BindTarget = settings.SwooshVolume } + }, + new SettingsSlider + { + LabelText = "Swoosh Pre-Delay (ms)", + Current = { BindTarget = settings.SwooshPreDelay } + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Swoosh Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.SwooshSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + } + } + }, + + #endregion + + #region impact + + // ==================== IMPACT ==================== + tabContainers[SectionTabs.Impact] = new FillFlowContainer + { + Alpha = 0, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Width = 1f, + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Play Impact", + Current = { BindTarget = settings.PlayImpact } + }, + new SettingsSlider + { + LabelText = "Impact Volume", + Current = { BindTarget = settings.ImpactVolume } + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade D Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ImpactGradeDSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade C Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ImpactGradeCSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade B Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ImpactGradeBSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade A Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ImpactGradeASampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade S Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ImpactGradeSSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade SS Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ImpactGradeSSSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + } + } + }, + + #endregion + + #region applause + + // ==================== APPLAUSE ==================== + tabContainers[SectionTabs.Applause] = new FillFlowContainer + { + Alpha = 0, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Width = 1f, + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Play Applause", + Current = { BindTarget = settings.PlayApplause } + }, + new SettingsSlider + { + LabelText = "Applause Volume", + Current = { BindTarget = settings.ApplauseVolume } + }, + new SettingsSlider + { + LabelText = "Applause Delay (ms)", + Current = { BindTarget = settings.ApplauseDelay } + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade D Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ApplauseGradeDSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade C Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ApplauseGradeCSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade B Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ApplauseGradeBSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade A Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ApplauseGradeASampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade S Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ApplauseGradeSSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade SS Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ApplauseGradeSSSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + } + } + }, + + #endregion + + #region preset + + // ==================== PRESET ==================== + tabContainers[SectionTabs.Preset] = new FillFlowContainer + { + Alpha = 0, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Width = 1f, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.Default.With(size: 24), + Text = "Load", + Colour = colours.Yellow + }, + presetFileSelector = new FileSelector(presetStorage.GetFullPath(string.Empty)) + { + RelativeSizeAxes = Axes.X, + Height = 300, + }, + new OsuSpriteText + { + Font = OsuFont.Default.With(size: 24), + Text = "Save", + Colour = colours.Yellow + }, + saveFilename = new OsuTextBox + { + PlaceholderText = "New preset filename", + RelativeSizeAxes = Axes.X, + }, + new TriangleButton + { + Text = "Save", + Action = savePreset, + RelativeSizeAxes = Axes.X, + }, + } + }, + + #endregion + + #region fileselector + + // ==================== SAMPLE SELECTOR ==================== + sampleSelectContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10) + { + Top = 20, + }, + Width = 1f, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.Default.With(size: 20), + Text = "Load Sample", + Colour = colours.Yellow + }, + sampleFileSelector = new FileSelector("/Users/jamie/Sandbox/derp/Samples/Results") + { + RelativeSizeAxes = Axes.X, + Height = 300, + }, + new TriangleButton + { + Text = "Refresh", + Action = refreshSampleBrowser, + RelativeSizeAxes = Axes.X, + }, + new SettingsEnumDropdown + { + Current = { BindTarget = sampleLoadTarget } + }, + new TriangleButton + { + Text = "Load Sample", + Action = loadSample, + RelativeSizeAxes = Axes.X, + } + } + } + + #endregion + } + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#555"), Color4Extensions.FromHex("#333")) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Children = new[] + { + new TriangleButton + { + Text = "Low D Rank", + Action = CreateLowRankDCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "D Rank", + Action = CreateDRankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "C Rank", + Action = CreateCRankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "B Rank", + Action = CreateBRankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "A Rank", + Action = CreateARankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "S Rank", + Action = CreateSRankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "Almost SS Rank", + Action = CreateAlmostSSRankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "SS Rank", + Action = CreateSSRankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + } + }, + accuracyCircle = new Container + { + RelativeSizeAxes = Axes.Both, + // Child = CreateRankDCircle() + } + } + } + } + }, + }; + + presetFileSelector.CurrentFile.ValueChanged += value => + { + string path = value.NewValue.FullName; + + loadPreset(path); + saveFilename.Text = Path.GetFileNameWithoutExtension(path); + }; + + sampleFileSelector.CurrentFile.ValueChanged += value => + { + var sample = Path.GetFileNameWithoutExtension(value.NewValue.Name); + + previewSampleChannel?.Dispose(); + previewSampleChannel = new DrawableSample(audioManager.Samples.Get($"Results/{sample}")); + previewSampleChannel?.Play(); + + selectedSampleName.Value = sample; + }; + + tabSelector.Current.ValueChanged += tab => + { + tabContainers[tab.OldValue].Hide(); + tabContainers[tab.NewValue].Show(); + + switch (tab.NewValue) + { + case SectionTabs.Preset: + sampleSelectContainer.Hide(); + break; + + case SectionTabs.Impact: + sampleLoadTarget.Value = SampleLoadTarget.ImpactD; + sampleSelectContainer.Show(); + break; + + case SectionTabs.Swoosh: + sampleLoadTarget.Value = SampleLoadTarget.Swoosh; + sampleSelectContainer.Show(); + break; + + case SectionTabs.BadgeDinks: + sampleLoadTarget.Value = SampleLoadTarget.BadgeDink; + sampleSelectContainer.Show(); + break; + + case SectionTabs.ScoreTicks: + sampleLoadTarget.Value = SampleLoadTarget.ScoreTick; + sampleSelectContainer.Show(); + break; + + case SectionTabs.Applause: + sampleLoadTarget.Value = SampleLoadTarget.ApplauseD; + sampleSelectContainer.Show(); + break; + } + }; + } + + #region rank scenarios + + [Test] + public void TestDoNothing() => AddStep("show", () => + { + /* do nothing */ + }); + + [Test] + public void TestLowDRank() => AddStep("show", CreateLowRankDCircle); + + [Test] + public void TestDRank() => AddStep("show", CreateDRankCircle); + + [Test] + public void TestCRank() => AddStep("show", CreateCRankCircle); + + [Test] + public void TestBRank() => AddStep("show", CreateBRankCircle); + + [Test] + public void TestARank() => AddStep("show", CreateARankCircle); + + [Test] + public void TestSRank() => AddStep("show", CreateSRankCircle); + + [Test] + public void TestAlmostSSRank() => AddStep("show", CreateAlmostSSRankCircle); + + [Test] + public void TestSSRank() => AddStep("show", CreateSSRankCircle); + + #endregion + + public void CreateLowRankDCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.2, ScoreRank.D)); + + public void CreateDRankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.5, ScoreRank.D)); + + public void CreateCRankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.75, ScoreRank.C)); + + public void CreateBRankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.85, ScoreRank.B)); + + public void CreateARankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.925, ScoreRank.A)); + + public void CreateSRankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.975, ScoreRank.S)); + + public void CreateAlmostSSRankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.9999, ScoreRank.S)); + + public void CreateSSRankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(1, ScoreRank.X)); + + public AccuracyCircle CreateAccuracyCircle(ScoreInfo score) + { + var newAccuracyCircle = new AccuracyCircle(score, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(230), + }; + + newAccuracyCircle.BindAudioSettings(settings); + + return newAccuracyCircle; + } + + private void savePreset() + { + string path = presetStorage.GetFullPath($"{saveFilename.Text}.json", true); + File.WriteAllText(path, JsonConvert.SerializeObject(settings)); + presetFileSelector.CurrentFile.Value = new FileInfo(path); + } + + private void loadPreset(string filename) + { + var saved = JsonConvert.DeserializeObject(File.ReadAllText(presetStorage.GetFullPath(filename))); + + foreach (var (_, prop) in saved.GetSettingsSourceProperties()) + { + var targetBindable = (IBindable)prop.GetValue(settings); + var sourceBindable = (IBindable)prop.GetValue(saved); + + ((IParseable)targetBindable)?.Parse(sourceBindable); + } + } + + private void refreshSampleBrowser() => + sampleFileSelector.CurrentPath.Value = new DirectoryInfo(sampleFileSelector.CurrentPath.Value.FullName); + + private void loadSample() + { + switch (sampleLoadTarget.Value) + { + case SampleLoadTarget.Swoosh: + settings.SwooshSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ScoreTick: + settings.TickSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.BadgeDink: + settings.BadgeSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.BadgeDinkMax: + settings.BadgeMaxSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ImpactD: + settings.ImpactGradeDSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ImpactC: + settings.ImpactGradeCSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ImpactB: + settings.ImpactGradeBSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ImpactA: + settings.ImpactGradeASampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ImpactS: + settings.ImpactGradeSSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ImpactSS: + settings.ImpactGradeSSSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ApplauseD: + settings.ApplauseGradeDSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ApplauseC: + settings.ApplauseGradeCSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ApplauseB: + settings.ApplauseGradeBSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ApplauseA: + settings.ApplauseGradeASampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ApplauseS: + settings.ApplauseGradeSSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ApplauseSS: + settings.ApplauseGradeSSSampleName.Value = selectedSampleName.Value; + break; + } + } + + private ScoreInfo createScore(double accuracy = 0.95, ScoreRank rank = ScoreRank.S) => new ScoreInfo + { + User = new User + { + Id = 2, + Username = "peppy", + }, + Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, + Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, + TotalScore = 2845370, + Accuracy = accuracy, + MaxCombo = 999, + Rank = rank, + Date = DateTimeOffset.Now, + Statistics = + { + { HitResult.Miss, 1 }, + { HitResult.Meh, 50 }, + { HitResult.Good, 100 }, + { HitResult.Great, 300 }, + } + }; + } +} diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index c70b4dd35b..82f2bc8c29 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -3,13 +3,18 @@ using System; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -79,14 +84,69 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private Container badges; private RankText rankText; - public AccuracyCircle(ScoreInfo score) + private DrawableSample scoreTickSound; + private DrawableSample badgeTickSound; + private DrawableSample badgeMaxSound; + private DrawableSample swooshUpSound; + private DrawableSample rankDImpactSound; + private DrawableSample rankBImpactSound; + private DrawableSample rankCImpactSound; + private DrawableSample rankAImpactSound; + private DrawableSample rankSImpactSound; + private DrawableSample rankSSImpactSound; + private DrawableSample rankDApplauseSound; + private DrawableSample rankBApplauseSound; + private DrawableSample rankCApplauseSound; + private DrawableSample rankAApplauseSound; + private DrawableSample rankSApplauseSound; + private DrawableSample rankSSApplauseSound; + + private Bindable tickPlaybackRate = new Bindable(); + private double lastTickPlaybackTime; + private bool isTicking; + + private AudioManager audioManager; + + public AccuracyCircleAudioSettings AudioSettings = new AccuracyCircleAudioSettings(); + + private readonly bool withFlair; + + public AccuracyCircle(ScoreInfo score, bool withFlair) { this.score = score; + this.withFlair = withFlair; + } + + public void BindAudioSettings(AccuracyCircleAudioSettings audioSettings) + { + foreach (var (_, prop) in audioSettings.GetSettingsSourceProperties()) + { + var targetBindable = (IBindable)prop.GetValue(AudioSettings); + var sourceBindable = (IBindable)prop.GetValue(audioSettings); + + targetBindable?.BindTo(sourceBindable); + } + } + + private void loadSample(ref DrawableSample target, string sampleName, [CanBeNull] BindableDouble volumeBindable = null) + { + if (IsDisposed) return; + + target?.Expire(); + AddInternal(target = new DrawableSample(audioManager.Samples.Get($"Results/{sampleName}")) + { + Frequency = { Value = 1.0 } + }); + + if (volumeBindable != null) + target.Volume.BindTarget = volumeBindable; } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load(AudioManager audio, GameHost host) { + audioManager = audio; + InternalChildren = new Drawable[] { new SmoothCircularProgress @@ -204,6 +264,35 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy }, rankText = new RankText(score.Rank) }; + + if (withFlair) + { + tickPlaybackRate = new Bindable(AudioSettings.TickDebounceStart.Value); + + // score ticks + AudioSettings.TickSampleName.BindValueChanged(sample => loadSample(ref scoreTickSound, sample.NewValue), true); + AudioSettings.SwooshSampleName.BindValueChanged(sample => loadSample(ref swooshUpSound, sample.NewValue, AudioSettings.SwooshVolume), true); + + // badge sounds + AudioSettings.BadgeSampleName.BindValueChanged(sample => loadSample(ref badgeTickSound, sample.NewValue, AudioSettings.BadgeDinkVolume), true); + AudioSettings.BadgeMaxSampleName.BindValueChanged(sample => loadSample(ref badgeMaxSound, sample.NewValue, AudioSettings.BadgeDinkVolume), true); + + // impacts + AudioSettings.ImpactGradeDSampleName.BindValueChanged(sample => loadSample(ref rankDImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); + AudioSettings.ImpactGradeCSampleName.BindValueChanged(sample => loadSample(ref rankCImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); + AudioSettings.ImpactGradeBSampleName.BindValueChanged(sample => loadSample(ref rankBImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); + AudioSettings.ImpactGradeASampleName.BindValueChanged(sample => loadSample(ref rankAImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); + AudioSettings.ImpactGradeSSampleName.BindValueChanged(sample => loadSample(ref rankSImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); + AudioSettings.ImpactGradeSSSampleName.BindValueChanged(sample => loadSample(ref rankSSImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); + + // applause + AudioSettings.ApplauseGradeDSampleName.BindValueChanged(sample => loadSample(ref rankDApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); + AudioSettings.ApplauseGradeCSampleName.BindValueChanged(sample => loadSample(ref rankCApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); + AudioSettings.ApplauseGradeBSampleName.BindValueChanged(sample => loadSample(ref rankBApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); + AudioSettings.ApplauseGradeASampleName.BindValueChanged(sample => loadSample(ref rankAApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); + AudioSettings.ApplauseGradeSSampleName.BindValueChanged(sample => loadSample(ref rankSApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); + AudioSettings.ApplauseGradeSSSampleName.BindValueChanged(sample => loadSample(ref rankSSApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); + } } private ScoreRank getRank(ScoreRank rank) @@ -214,12 +303,29 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy return rank; } + protected override void Update() + { + base.Update(); + + if (!AudioSettings.PlayTicks.Value || !isTicking) return; + + bool enoughTimePassedSinceLastPlayback = Clock.CurrentTime - lastTickPlaybackTime >= tickPlaybackRate.Value; + + if (!enoughTimePassedSinceLastPlayback) return; + + scoreTickSound?.Play(); + lastTickPlaybackTime = Clock.CurrentTime; + } + protected override void LoadComplete() { base.LoadComplete(); this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint); + if (AudioSettings.PlaySwooshSound.Value) + this.Delay(AudioSettings.SwooshPreDelay.Value).Schedule(() => swooshUpSound?.Play()); + using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY, true)) innerMask.FillTo(1f, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); @@ -229,6 +335,22 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); + if (AudioSettings.PlayTicks.Value) + { + scoreTickSound?.FrequencyTo(1 + (targetAccuracy * AudioSettings.TickPitchFactor.Value), ACCURACY_TRANSFORM_DURATION, AudioSettings.TickPitchEasing.Value); + scoreTickSound?.VolumeTo(AudioSettings.TickVolumeStart.Value).Then().VolumeTo(AudioSettings.TickVolumeEnd.Value, ACCURACY_TRANSFORM_DURATION, AudioSettings.TickVolumeEasing.Value); + this.TransformBindableTo(tickPlaybackRate, AudioSettings.TickDebounceEnd.Value, ACCURACY_TRANSFORM_DURATION, AudioSettings.TickRateEasing.Value); + } + + Schedule(() => + { + if (!AudioSettings.PlayTicks.Value) return; + + isTicking = true; + }); + + int badgeNum = 0; + foreach (var badge in badges) { if (badge.Accuracy > score.Accuracy) @@ -237,12 +359,100 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(1 - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION, true)) { badge.Appear(); + Schedule(() => + { + if (badgeTickSound == null || badgeMaxSound == null || !AudioSettings.PlayBadgeSounds.Value) return; + + if (badgeNum < (badges.Count - 1)) + { + badgeTickSound.Frequency.Value = 1 + (badgeNum++ * 0.05); + badgeTickSound?.Play(); + } + else + { + badgeMaxSound.Frequency.Value = 1 + (badgeNum++ * 0.05); + badgeMaxSound?.Play(); + isTicking = false; + } + }); } } using (BeginDelayedSequence(TEXT_APPEAR_DELAY, true)) { rankText.Appear(); + Schedule(() => + { + isTicking = false; + + if (!AudioSettings.PlayImpact.Value) return; + + switch (score.Rank) + { + case ScoreRank.D: + rankDImpactSound?.Play(); + break; + + case ScoreRank.C: + rankCImpactSound?.Play(); + break; + + case ScoreRank.B: + rankBImpactSound?.Play(); + break; + + case ScoreRank.A: + rankAImpactSound?.Play(); + break; + + case ScoreRank.S: + case ScoreRank.SH: + rankSImpactSound?.Play(); + break; + + case ScoreRank.X: + case ScoreRank.XH: + rankSSImpactSound?.Play(); + break; + } + }); + + using (BeginDelayedSequence(AudioSettings.ApplauseDelay.Value)) + { + if (!AudioSettings.PlayApplause.Value) return; + + Schedule(() => + { + switch (score.Rank) + { + case ScoreRank.D: + rankDApplauseSound?.Play(); + break; + + case ScoreRank.C: + rankCApplauseSound?.Play(); + break; + + case ScoreRank.B: + rankBApplauseSound?.Play(); + break; + + case ScoreRank.A: + rankAApplauseSound?.Play(); + break; + + case ScoreRank.S: + case ScoreRank.SH: + rankSApplauseSound?.Play(); + break; + + case ScoreRank.X: + case ScoreRank.XH: + rankSSApplauseSound?.Play(); + break; + } + }); + } } } } @@ -266,4 +476,164 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy return test; } } + + public class AccuracyCircleAudioSettings + { + [SettingSource("setting")] + public Bindable PlayTicks { get; } = new Bindable(true); + + [SettingSource("setting")] + public Bindable TickSampleName { get; } = new Bindable("badge-dink-2"); + + [SettingSource("setting")] + public Bindable PlayBadgeSounds { get; } = new Bindable(true); + + [SettingSource("setting")] + public Bindable BadgeSampleName { get; } = new Bindable("badge-dink-3"); + + [SettingSource("setting")] + public Bindable BadgeMaxSampleName { get; } = new Bindable("badge-dink-8"); + + [SettingSource("setting")] + public Bindable PlaySwooshSound { get; } = new Bindable(true); + + [SettingSource("setting")] + public Bindable SwooshSampleName { get; } = new Bindable("swoosh-up-2"); + + [SettingSource("setting")] + public Bindable PlayImpact { get; } = new Bindable(true); + + [SettingSource("setting")] + public Bindable ImpactGradeDSampleName { get; } = new Bindable("rank-impact-d-1"); + + [SettingSource("setting")] + public Bindable ImpactGradeCSampleName { get; } = new Bindable("rank-impact-c-3"); + + [SettingSource("setting")] + public Bindable ImpactGradeBSampleName { get; } = new Bindable("rank-impact-b-3"); + + [SettingSource("setting")] + public Bindable ImpactGradeASampleName { get; } = new Bindable("rank-impact-a-3"); + + [SettingSource("setting")] + public Bindable ImpactGradeSSampleName { get; } = new Bindable("rank-impact-s-3"); + + [SettingSource("setting")] + public Bindable ImpactGradeSSSampleName { get; } = new Bindable("rank-impact-s-3"); + + [SettingSource("setting")] + public Bindable PlayApplause { get; } = new Bindable(true); + + [SettingSource("setting")] + public BindableDouble ApplauseVolume { get; } = new BindableDouble(1) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1, + }; + + [SettingSource("setting")] + public BindableDouble ApplauseDelay { get; } = new BindableDouble(545) + { + MinValue = 0, + MaxValue = 10000, + Precision = 1, + }; + + [SettingSource("setting")] + public Bindable ApplauseGradeDSampleName { get; } = new Bindable("rank-applause-d-1"); + + [SettingSource("setting")] + public Bindable ApplauseGradeCSampleName { get; } = new Bindable("rank-applause-c-1"); + + [SettingSource("setting")] + public Bindable ApplauseGradeBSampleName { get; } = new Bindable("rank-applause-b-1"); + + [SettingSource("setting")] + public Bindable ApplauseGradeASampleName { get; } = new Bindable("rank-applause-a-1"); + + [SettingSource("setting")] + public Bindable ApplauseGradeSSampleName { get; } = new Bindable("rank-applause-s-1"); + + [SettingSource("setting")] + public Bindable ApplauseGradeSSSampleName { get; } = new Bindable("rank-applause-s-1"); + + [SettingSource("setting")] + public BindableDouble TickPitchFactor { get; } = new BindableDouble(1) + { + MinValue = 0, + MaxValue = 3, + Precision = 0.1, + }; + + [SettingSource("setting")] + public BindableDouble TickDebounceStart { get; } = new BindableDouble(10) + { + MinValue = 1, + MaxValue = 100, + }; + + [SettingSource("setting")] + public BindableDouble TickDebounceEnd { get; } = new BindableDouble(400) + { + MinValue = 100, + MaxValue = 1000, + }; + + [SettingSource("setting")] + public BindableDouble SwooshPreDelay { get; } = new BindableDouble(450) + { + MinValue = -1000, + MaxValue = 1000, + }; + + [SettingSource("setting")] + public Bindable TickRateEasing { get; } = new Bindable(Easing.None); + + [SettingSource("setting")] + public Bindable TickPitchEasing { get; } = new Bindable(Easing.None); + + [SettingSource("setting")] + public Bindable TickVolumeEasing { get; } = new Bindable(Easing.OutSine); + + [SettingSource("setting")] + public BindableDouble TickVolumeStart { get; } = new BindableDouble(0.6) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1, + }; + + [SettingSource("setting")] + public BindableDouble TickVolumeEnd { get; } = new BindableDouble(1.0) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1, + }; + + [SettingSource("setting")] + public BindableDouble ImpactVolume { get; } = new BindableDouble(1.0) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1, + }; + + [SettingSource("setting")] + public BindableDouble BadgeDinkVolume { get; } = new BindableDouble(0.5) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1, + }; + + [SettingSource("setting")] + public BindableDouble SwooshVolume { get; } = new BindableDouble(0.5) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1, + }; + } } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 4895240314..6a6b39b61c 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Ranking.Expanded Margin = new MarginPadding { Top = 40 }, RelativeSizeAxes = Axes.X, Height = 230, - Child = new AccuracyCircle(score) + Child = new AccuracyCircle(score, withFlair) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index a0ea27b640..95dd9f72a8 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -156,13 +156,6 @@ namespace osu.Game.Screens.Ranking bool shouldFlair = player != null && !Score.Mods.Any(m => m is ModAutoplay); ScorePanelList.AddScore(Score, shouldFlair); - - if (shouldFlair) - { - AddInternal(applauseSound = Score.Rank >= ScoreRank.A - ? new SkinnableSound(new SampleInfo("Results/rankpass", "applause")) - : new SkinnableSound(new SampleInfo("Results/rankfail"))); - } } if (allowWatchingReplay) From 30eff8cc2ac400be81c2acc3caea416b4b86cf23 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 27 May 2021 15:10:37 +0900 Subject: [PATCH 192/520] remove overlapping/legacy applause --- osu.Game/Screens/Ranking/ResultsScreen.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 95dd9f72a8..c1f5d92d17 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Screens; -using osu.Game.Audio; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; @@ -20,20 +19,13 @@ using osu.Game.Online.API; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Ranking.Expanded.Accuracy; using osu.Game.Screens.Ranking.Statistics; -using osu.Game.Skinning; using osuTK; namespace osu.Game.Screens.Ranking { public abstract class ResultsScreen : ScreenWithBeatmapBackground, IKeyBindingHandler { - /// - /// Delay before the default applause sound should be played, in order to match the grade display timing in . - /// - public const double APPLAUSE_DELAY = AccuracyCircle.ACCURACY_TRANSFORM_DELAY + AccuracyCircle.TEXT_APPEAR_DELAY + ScorePanel.RESIZE_DURATION + ScorePanel.TOP_LAYER_EXPAND_DELAY - 1440; - protected const float BACKGROUND_BLUR = 20; private static readonly float screen_height = 768 - TwoLayerButton.SIZE_EXTENDED.Y; @@ -64,8 +56,6 @@ namespace osu.Game.Screens.Ranking private readonly bool allowRetry; private readonly bool allowWatchingReplay; - private SkinnableSound applauseSound; - protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true) { Score = score; @@ -193,9 +183,6 @@ namespace osu.Game.Screens.Ranking api.Queue(req); statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true); - - using (BeginDelayedSequence(APPLAUSE_DELAY)) - Schedule(() => applauseSound?.Play()); } protected override void Update() From 63e5bc454315ba36c1d011c0b2deeb19fe9757c6 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 27 May 2021 16:37:08 +0900 Subject: [PATCH 193/520] update sample names and timings --- .../Expanded/Accuracy/AccuracyCircle.cs | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 82f2bc8c29..0a2442015e 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -483,53 +483,53 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy public Bindable PlayTicks { get; } = new Bindable(true); [SettingSource("setting")] - public Bindable TickSampleName { get; } = new Bindable("badge-dink-2"); + public Bindable TickSampleName { get; } = new Bindable("score-tick"); [SettingSource("setting")] public Bindable PlayBadgeSounds { get; } = new Bindable(true); [SettingSource("setting")] - public Bindable BadgeSampleName { get; } = new Bindable("badge-dink-3"); + public Bindable BadgeSampleName { get; } = new Bindable("badge-dink"); [SettingSource("setting")] - public Bindable BadgeMaxSampleName { get; } = new Bindable("badge-dink-8"); + public Bindable BadgeMaxSampleName { get; } = new Bindable("badge-dink-max"); [SettingSource("setting")] public Bindable PlaySwooshSound { get; } = new Bindable(true); [SettingSource("setting")] - public Bindable SwooshSampleName { get; } = new Bindable("swoosh-up-2"); + public Bindable SwooshSampleName { get; } = new Bindable("swoosh-up"); [SettingSource("setting")] public Bindable PlayImpact { get; } = new Bindable(true); [SettingSource("setting")] - public Bindable ImpactGradeDSampleName { get; } = new Bindable("rank-impact-d-1"); + public Bindable ImpactGradeDSampleName { get; } = new Bindable("rank-impact-fail-d"); [SettingSource("setting")] - public Bindable ImpactGradeCSampleName { get; } = new Bindable("rank-impact-c-3"); + public Bindable ImpactGradeCSampleName { get; } = new Bindable("rank-impact-fail"); [SettingSource("setting")] - public Bindable ImpactGradeBSampleName { get; } = new Bindable("rank-impact-b-3"); + public Bindable ImpactGradeBSampleName { get; } = new Bindable("rank-impact-fail"); [SettingSource("setting")] - public Bindable ImpactGradeASampleName { get; } = new Bindable("rank-impact-a-3"); + public Bindable ImpactGradeASampleName { get; } = new Bindable("rank-impact-pass"); [SettingSource("setting")] - public Bindable ImpactGradeSSampleName { get; } = new Bindable("rank-impact-s-3"); + public Bindable ImpactGradeSSampleName { get; } = new Bindable("rank-impact-pass"); [SettingSource("setting")] - public Bindable ImpactGradeSSSampleName { get; } = new Bindable("rank-impact-s-3"); + public Bindable ImpactGradeSSSampleName { get; } = new Bindable("rank-impact-pass-ss"); [SettingSource("setting")] public Bindable PlayApplause { get; } = new Bindable(true); [SettingSource("setting")] - public BindableDouble ApplauseVolume { get; } = new BindableDouble(1) + public BindableDouble ApplauseVolume { get; } = new BindableDouble(0.8) { MinValue = 0, MaxValue = 1, - Precision = 0.1, + Precision = 0.1 }; [SettingSource("setting")] @@ -537,61 +537,61 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { MinValue = 0, MaxValue = 10000, - Precision = 1, + Precision = 1 }; [SettingSource("setting")] - public Bindable ApplauseGradeDSampleName { get; } = new Bindable("rank-applause-d-1"); + public Bindable ApplauseGradeDSampleName { get; } = new Bindable("applause-d"); [SettingSource("setting")] - public Bindable ApplauseGradeCSampleName { get; } = new Bindable("rank-applause-c-1"); + public Bindable ApplauseGradeCSampleName { get; } = new Bindable("applause-c"); [SettingSource("setting")] - public Bindable ApplauseGradeBSampleName { get; } = new Bindable("rank-applause-b-1"); + public Bindable ApplauseGradeBSampleName { get; } = new Bindable("applause-b"); [SettingSource("setting")] - public Bindable ApplauseGradeASampleName { get; } = new Bindable("rank-applause-a-1"); + public Bindable ApplauseGradeASampleName { get; } = new Bindable("applause-a"); [SettingSource("setting")] - public Bindable ApplauseGradeSSampleName { get; } = new Bindable("rank-applause-s-1"); + public Bindable ApplauseGradeSSampleName { get; } = new Bindable("applause-s"); [SettingSource("setting")] - public Bindable ApplauseGradeSSSampleName { get; } = new Bindable("rank-applause-s-1"); + public Bindable ApplauseGradeSSSampleName { get; } = new Bindable("applause-s"); [SettingSource("setting")] public BindableDouble TickPitchFactor { get; } = new BindableDouble(1) { MinValue = 0, MaxValue = 3, - Precision = 0.1, + Precision = 0.1 }; [SettingSource("setting")] - public BindableDouble TickDebounceStart { get; } = new BindableDouble(10) + public BindableDouble TickDebounceStart { get; } = new BindableDouble(18) { MinValue = 1, - MaxValue = 100, + MaxValue = 100 }; [SettingSource("setting")] - public BindableDouble TickDebounceEnd { get; } = new BindableDouble(400) + public BindableDouble TickDebounceEnd { get; } = new BindableDouble(300) { MinValue = 100, - MaxValue = 1000, + MaxValue = 1000 }; [SettingSource("setting")] - public BindableDouble SwooshPreDelay { get; } = new BindableDouble(450) + public BindableDouble SwooshPreDelay { get; } = new BindableDouble(443) { MinValue = -1000, - MaxValue = 1000, + MaxValue = 1000 }; [SettingSource("setting")] - public Bindable TickRateEasing { get; } = new Bindable(Easing.None); + public Bindable TickRateEasing { get; } = new Bindable(Easing.OutSine); [SettingSource("setting")] - public Bindable TickPitchEasing { get; } = new Bindable(Easing.None); + public Bindable TickPitchEasing { get; } = new Bindable(Easing.OutSine); [SettingSource("setting")] public Bindable TickVolumeEasing { get; } = new Bindable(Easing.OutSine); @@ -601,7 +601,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { MinValue = 0, MaxValue = 1, - Precision = 0.1, + Precision = 0.1 }; [SettingSource("setting")] @@ -609,7 +609,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { MinValue = 0, MaxValue = 1, - Precision = 0.1, + Precision = 0.1 }; [SettingSource("setting")] @@ -617,23 +617,23 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { MinValue = 0, MaxValue = 1, - Precision = 0.1, + Precision = 0.1 }; [SettingSource("setting")] - public BindableDouble BadgeDinkVolume { get; } = new BindableDouble(0.5) + public BindableDouble BadgeDinkVolume { get; } = new BindableDouble(1) { MinValue = 0, MaxValue = 1, - Precision = 0.1, + Precision = 0.1 }; [SettingSource("setting")] - public BindableDouble SwooshVolume { get; } = new BindableDouble(0.5) + public BindableDouble SwooshVolume { get; } = new BindableDouble(0.4) { MinValue = 0, MaxValue = 1, - Precision = 0.1, + Precision = 0.1 }; } } From 8dc595d201919233a09c78bcaec2816bb846cada Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 28 May 2021 19:27:55 +0900 Subject: [PATCH 194/520] move result screen samples to DefaultSkin --- .../SoundDesign/TestSceneAccuracyCircle.cs | 164 +++++++- .../Expanded/Accuracy/AccuracyCircle.cs | 360 +++++------------- osu.Game/Skinning/DefaultSkin.cs | 55 +++ osu.Game/Skinning/GameplaySkinSamples.cs | 29 ++ osu.Game/Skinning/LegacySkin.cs | 38 ++ 5 files changed, 387 insertions(+), 259 deletions(-) create mode 100644 osu.Game/Skinning/GameplaySkinSamples.cs diff --git a/osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs index c7ff7f9760..e630bb5983 100644 --- a/osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs @@ -36,7 +36,6 @@ using osuTK; namespace osu.Game.Tests.Visual.SoundDesign { - [Serializable] public class TestSceneAccuracyCircle : OsuTestScene { [Resolved] @@ -845,7 +844,7 @@ namespace osu.Game.Tests.Visual.SoundDesign Size = new Vector2(230), }; - newAccuracyCircle.BindAudioSettings(settings); + // newAccuracyCircle.BindAudioSettings(settings); return newAccuracyCircle; } @@ -966,4 +965,165 @@ namespace osu.Game.Tests.Visual.SoundDesign } }; } + + [Serializable] + public class AccuracyCircleAudioSettings + { + [SettingSource("setting")] + public Bindable PlayTicks { get; } = new Bindable(true); + + [SettingSource("setting")] + public Bindable TickSampleName { get; } = new Bindable("score-tick"); + + [SettingSource("setting")] + public Bindable PlayBadgeSounds { get; } = new Bindable(true); + + [SettingSource("setting")] + public Bindable BadgeSampleName { get; } = new Bindable("badge-dink"); + + [SettingSource("setting")] + public Bindable BadgeMaxSampleName { get; } = new Bindable("badge-dink-max"); + + [SettingSource("setting")] + public Bindable PlaySwooshSound { get; } = new Bindable(true); + + [SettingSource("setting")] + public Bindable SwooshSampleName { get; } = new Bindable("swoosh-up"); + + [SettingSource("setting")] + public Bindable PlayImpact { get; } = new Bindable(true); + + [SettingSource("setting")] + public Bindable ImpactGradeDSampleName { get; } = new Bindable("rank-impact-fail-d"); + + [SettingSource("setting")] + public Bindable ImpactGradeCSampleName { get; } = new Bindable("rank-impact-fail"); + + [SettingSource("setting")] + public Bindable ImpactGradeBSampleName { get; } = new Bindable("rank-impact-fail"); + + [SettingSource("setting")] + public Bindable ImpactGradeASampleName { get; } = new Bindable("rank-impact-pass"); + + [SettingSource("setting")] + public Bindable ImpactGradeSSampleName { get; } = new Bindable("rank-impact-pass"); + + [SettingSource("setting")] + public Bindable ImpactGradeSSSampleName { get; } = new Bindable("rank-impact-pass-ss"); + + [SettingSource("setting")] + public Bindable PlayApplause { get; } = new Bindable(true); + + [SettingSource("setting")] + public BindableDouble ApplauseVolume { get; } = new BindableDouble(0.8) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1 + }; + + [SettingSource("setting")] + public BindableDouble ApplauseDelay { get; } = new BindableDouble(545) + { + MinValue = 0, + MaxValue = 10000, + Precision = 1 + }; + + [SettingSource("setting")] + public Bindable ApplauseGradeDSampleName { get; } = new Bindable("applause-d"); + + [SettingSource("setting")] + public Bindable ApplauseGradeCSampleName { get; } = new Bindable("applause-c"); + + [SettingSource("setting")] + public Bindable ApplauseGradeBSampleName { get; } = new Bindable("applause-b"); + + [SettingSource("setting")] + public Bindable ApplauseGradeASampleName { get; } = new Bindable("applause-a"); + + [SettingSource("setting")] + public Bindable ApplauseGradeSSampleName { get; } = new Bindable("applause-s"); + + [SettingSource("setting")] + public Bindable ApplauseGradeSSSampleName { get; } = new Bindable("applause-s"); + + [SettingSource("setting")] + public BindableDouble TickPitchFactor { get; } = new BindableDouble(1) + { + MinValue = 0, + MaxValue = 3, + Precision = 0.1 + }; + + [SettingSource("setting")] + public BindableDouble TickDebounceStart { get; } = new BindableDouble(18) + { + MinValue = 1, + MaxValue = 100 + }; + + [SettingSource("setting")] + public BindableDouble TickDebounceEnd { get; } = new BindableDouble(300) + { + MinValue = 100, + MaxValue = 1000 + }; + + [SettingSource("setting")] + public BindableDouble SwooshPreDelay { get; } = new BindableDouble(443) + { + MinValue = -1000, + MaxValue = 1000 + }; + + [SettingSource("setting")] + public Bindable TickRateEasing { get; } = new Bindable(Easing.OutSine); + + [SettingSource("setting")] + public Bindable TickPitchEasing { get; } = new Bindable(Easing.OutSine); + + [SettingSource("setting")] + public Bindable TickVolumeEasing { get; } = new Bindable(Easing.OutSine); + + [SettingSource("setting")] + public BindableDouble TickVolumeStart { get; } = new BindableDouble(0.6) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1 + }; + + [SettingSource("setting")] + public BindableDouble TickVolumeEnd { get; } = new BindableDouble(1.0) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1 + }; + + [SettingSource("setting")] + public BindableDouble ImpactVolume { get; } = new BindableDouble(1.0) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1 + }; + + [SettingSource("setting")] + public BindableDouble BadgeDinkVolume { get; } = new BindableDouble(1) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1 + }; + + [SettingSource("setting")] + public BindableDouble SwooshVolume { get; } = new BindableDouble(0.4) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1 + }; + } } diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 0a2442015e..5cf41513c8 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -12,12 +11,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Utils; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Screens.Ranking.Expanded.Accuracy @@ -77,6 +77,27 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy /// public static readonly Easing ACCURACY_TRANSFORM_EASING = Easing.OutPow10; + // audio sfx parameters + public bool PlayTicks = true; + public bool PlayBadgeSounds = true; + public bool PlaySwooshSound = true; + public bool PlayImpact = true; + public bool PlayApplause = true; + public double ApplauseVolume = 0.8f; + public double ApplauseDelay = 545f; + public double TickPitchFactor = 1f; + public double TickDebounceStart = 18f; + public double TickDebounceEnd = 300f; + public double SwooshPreDelay = 443f; + public Easing TickRateEasing = Easing.OutSine; + public Easing TickPitchEasing = Easing.OutSine; + public Easing TickVolumeEasing = Easing.OutSine; + public double TickVolumeStart = 0.6f; + public double TickVolumeEnd = 1.0f; + public double ImpactVolume = 1.0f; + public double BadgeDinkVolume = 1f; + public double SwooshVolume = 0.4f; + private readonly ScoreInfo score; private SmoothCircularProgress accuracyCircle; @@ -107,8 +128,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private AudioManager audioManager; - public AccuracyCircleAudioSettings AudioSettings = new AccuracyCircleAudioSettings(); - private readonly bool withFlair; public AccuracyCircle(ScoreInfo score, bool withFlair) @@ -117,33 +136,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy this.withFlair = withFlair; } - public void BindAudioSettings(AccuracyCircleAudioSettings audioSettings) - { - foreach (var (_, prop) in audioSettings.GetSettingsSourceProperties()) - { - var targetBindable = (IBindable)prop.GetValue(AudioSettings); - var sourceBindable = (IBindable)prop.GetValue(audioSettings); - - targetBindable?.BindTo(sourceBindable); - } - } - - private void loadSample(ref DrawableSample target, string sampleName, [CanBeNull] BindableDouble volumeBindable = null) - { - if (IsDisposed) return; - - target?.Expire(); - AddInternal(target = new DrawableSample(audioManager.Samples.Get($"Results/{sampleName}")) - { - Frequency = { Value = 1.0 } - }); - - if (volumeBindable != null) - target.Volume.BindTarget = volumeBindable; - } - [BackgroundDependencyLoader] - private void load(AudioManager audio, GameHost host) + private void load(AudioManager audio, GameHost host, ISkinSource skin) { audioManager = audio; @@ -267,31 +261,27 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (withFlair) { - tickPlaybackRate = new Bindable(AudioSettings.TickDebounceStart.Value); + tickPlaybackRate = new Bindable(TickDebounceStart); - // score ticks - AudioSettings.TickSampleName.BindValueChanged(sample => loadSample(ref scoreTickSound, sample.NewValue), true); - AudioSettings.SwooshSampleName.BindValueChanged(sample => loadSample(ref swooshUpSound, sample.NewValue, AudioSettings.SwooshVolume), true); - - // badge sounds - AudioSettings.BadgeSampleName.BindValueChanged(sample => loadSample(ref badgeTickSound, sample.NewValue, AudioSettings.BadgeDinkVolume), true); - AudioSettings.BadgeMaxSampleName.BindValueChanged(sample => loadSample(ref badgeMaxSound, sample.NewValue, AudioSettings.BadgeDinkVolume), true); - - // impacts - AudioSettings.ImpactGradeDSampleName.BindValueChanged(sample => loadSample(ref rankDImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); - AudioSettings.ImpactGradeCSampleName.BindValueChanged(sample => loadSample(ref rankCImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); - AudioSettings.ImpactGradeBSampleName.BindValueChanged(sample => loadSample(ref rankBImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); - AudioSettings.ImpactGradeASampleName.BindValueChanged(sample => loadSample(ref rankAImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); - AudioSettings.ImpactGradeSSampleName.BindValueChanged(sample => loadSample(ref rankSImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); - AudioSettings.ImpactGradeSSSampleName.BindValueChanged(sample => loadSample(ref rankSSImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); - - // applause - AudioSettings.ApplauseGradeDSampleName.BindValueChanged(sample => loadSample(ref rankDApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); - AudioSettings.ApplauseGradeCSampleName.BindValueChanged(sample => loadSample(ref rankCApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); - AudioSettings.ApplauseGradeBSampleName.BindValueChanged(sample => loadSample(ref rankBApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); - AudioSettings.ApplauseGradeASampleName.BindValueChanged(sample => loadSample(ref rankAApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); - AudioSettings.ApplauseGradeSSampleName.BindValueChanged(sample => loadSample(ref rankSApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); - AudioSettings.ApplauseGradeSSSampleName.BindValueChanged(sample => loadSample(ref rankSSApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); + AddRangeInternal(new Drawable[] + { + scoreTickSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultScoreTick)) as DrawableSample, + badgeTickSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultBadgeTick)) as DrawableSample, + badgeMaxSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultBadgeTickMax)) as DrawableSample, + swooshUpSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultSwooshUp)) as DrawableSample, + rankDImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_D)) as DrawableSample, + rankBImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_B)) as DrawableSample, + rankCImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_C)) as DrawableSample, + rankAImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_A)) as DrawableSample, + rankSImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_S)) as DrawableSample, + rankSSImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_SS)) as DrawableSample, + rankDApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_D)) as DrawableSample, + rankBApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_B)) as DrawableSample, + rankCApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_C)) as DrawableSample, + rankAApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_A)) as DrawableSample, + rankSApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_S)) as DrawableSample, + rankSSApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_SS)) as DrawableSample + }); } } @@ -307,13 +297,13 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { base.Update(); - if (!AudioSettings.PlayTicks.Value || !isTicking) return; + if (!PlayTicks || !isTicking) return; bool enoughTimePassedSinceLastPlayback = Clock.CurrentTime - lastTickPlaybackTime >= tickPlaybackRate.Value; if (!enoughTimePassedSinceLastPlayback) return; - scoreTickSound?.Play(); + Schedule(() => scoreTickSound?.Play()); lastTickPlaybackTime = Clock.CurrentTime; } @@ -323,28 +313,35 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint); - if (AudioSettings.PlaySwooshSound.Value) - this.Delay(AudioSettings.SwooshPreDelay.Value).Schedule(() => swooshUpSound?.Play()); + if (PlaySwooshSound && swooshUpSound != null) + { + this.Delay(SwooshPreDelay).Schedule(() => + { + swooshUpSound.Volume.Value = SwooshVolume; + swooshUpSound.Play(); + }); + } - using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY, true)) + using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY)) innerMask.FillTo(1f, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); - using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY, true)) + using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY)) { double targetAccuracy = score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH ? 1 : Math.Min(1 - virtual_ss_percentage, score.Accuracy); accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); - if (AudioSettings.PlayTicks.Value) - { - scoreTickSound?.FrequencyTo(1 + (targetAccuracy * AudioSettings.TickPitchFactor.Value), ACCURACY_TRANSFORM_DURATION, AudioSettings.TickPitchEasing.Value); - scoreTickSound?.VolumeTo(AudioSettings.TickVolumeStart.Value).Then().VolumeTo(AudioSettings.TickVolumeEnd.Value, ACCURACY_TRANSFORM_DURATION, AudioSettings.TickVolumeEasing.Value); - this.TransformBindableTo(tickPlaybackRate, AudioSettings.TickDebounceEnd.Value, ACCURACY_TRANSFORM_DURATION, AudioSettings.TickRateEasing.Value); - } - Schedule(() => { - if (!AudioSettings.PlayTicks.Value) return; + if (!PlayTicks) return; + + if (scoreTickSound != null) + { + // doesn't work + scoreTickSound.FrequencyTo(1).Then().FrequencyTo(1 + targetAccuracy * TickPitchFactor, ACCURACY_TRANSFORM_DURATION, TickPitchEasing); + scoreTickSound.VolumeTo(TickVolumeStart).Then().VolumeTo(TickVolumeEnd, ACCURACY_TRANSFORM_DURATION, TickVolumeEasing); + this.TransformBindableTo(tickPlaybackRate, TickDebounceEnd, ACCURACY_TRANSFORM_DURATION, TickRateEasing); + } isTicking = true; }); @@ -359,98 +356,107 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(1 - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION, true)) { badge.Appear(); + + if (!PlayBadgeSounds) return; + Schedule(() => { - if (badgeTickSound == null || badgeMaxSound == null || !AudioSettings.PlayBadgeSounds.Value) return; - - if (badgeNum < (badges.Count - 1)) - { - badgeTickSound.Frequency.Value = 1 + (badgeNum++ * 0.05); - badgeTickSound?.Play(); - } - else - { - badgeMaxSound.Frequency.Value = 1 + (badgeNum++ * 0.05); - badgeMaxSound?.Play(); - isTicking = false; - } + DrawableSample dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound; + dink.FrequencyTo(1 + badgeNum++ * 0.05); + dink.VolumeTo(BadgeDinkVolume); + dink.Play(); }); } } - using (BeginDelayedSequence(TEXT_APPEAR_DELAY, true)) + using (BeginDelayedSequence(TEXT_APPEAR_DELAY)) { rankText.Appear(); + Schedule(() => { isTicking = false; - if (!AudioSettings.PlayImpact.Value) return; + if (!PlayImpact) return; + + DrawableSample impact = null; switch (score.Rank) { case ScoreRank.D: - rankDImpactSound?.Play(); + impact = rankDImpactSound; break; case ScoreRank.C: - rankCImpactSound?.Play(); + impact = rankCImpactSound; break; case ScoreRank.B: - rankBImpactSound?.Play(); + impact = rankBImpactSound; break; case ScoreRank.A: - rankAImpactSound?.Play(); + impact = rankAImpactSound; break; case ScoreRank.S: case ScoreRank.SH: - rankSImpactSound?.Play(); + impact = rankSImpactSound; break; case ScoreRank.X: case ScoreRank.XH: - rankSSImpactSound?.Play(); + impact = rankSSImpactSound; break; } + + if (impact == null) return; + + impact.Volume.Value = ImpactVolume; + impact.Play(); }); - using (BeginDelayedSequence(AudioSettings.ApplauseDelay.Value)) + using (BeginDelayedSequence(ApplauseDelay)) { - if (!AudioSettings.PlayApplause.Value) return; + if (!PlayApplause) return; Schedule(() => { + DrawableSample applause = null; + switch (score.Rank) { case ScoreRank.D: - rankDApplauseSound?.Play(); + applause = rankDApplauseSound; break; case ScoreRank.C: - rankCApplauseSound?.Play(); + applause = rankCApplauseSound; break; case ScoreRank.B: - rankBApplauseSound?.Play(); + applause = rankBApplauseSound; break; case ScoreRank.A: - rankAApplauseSound?.Play(); + applause = rankAApplauseSound; break; case ScoreRank.S: case ScoreRank.SH: - rankSApplauseSound?.Play(); + applause = rankSApplauseSound; break; case ScoreRank.X: case ScoreRank.XH: - rankSSApplauseSound?.Play(); + applause = rankSSApplauseSound; break; } + + if (applause == null) return; + + applause.Volume.Value = ApplauseVolume; + applause.Play(); }); } } @@ -476,164 +482,4 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy return test; } } - - public class AccuracyCircleAudioSettings - { - [SettingSource("setting")] - public Bindable PlayTicks { get; } = new Bindable(true); - - [SettingSource("setting")] - public Bindable TickSampleName { get; } = new Bindable("score-tick"); - - [SettingSource("setting")] - public Bindable PlayBadgeSounds { get; } = new Bindable(true); - - [SettingSource("setting")] - public Bindable BadgeSampleName { get; } = new Bindable("badge-dink"); - - [SettingSource("setting")] - public Bindable BadgeMaxSampleName { get; } = new Bindable("badge-dink-max"); - - [SettingSource("setting")] - public Bindable PlaySwooshSound { get; } = new Bindable(true); - - [SettingSource("setting")] - public Bindable SwooshSampleName { get; } = new Bindable("swoosh-up"); - - [SettingSource("setting")] - public Bindable PlayImpact { get; } = new Bindable(true); - - [SettingSource("setting")] - public Bindable ImpactGradeDSampleName { get; } = new Bindable("rank-impact-fail-d"); - - [SettingSource("setting")] - public Bindable ImpactGradeCSampleName { get; } = new Bindable("rank-impact-fail"); - - [SettingSource("setting")] - public Bindable ImpactGradeBSampleName { get; } = new Bindable("rank-impact-fail"); - - [SettingSource("setting")] - public Bindable ImpactGradeASampleName { get; } = new Bindable("rank-impact-pass"); - - [SettingSource("setting")] - public Bindable ImpactGradeSSampleName { get; } = new Bindable("rank-impact-pass"); - - [SettingSource("setting")] - public Bindable ImpactGradeSSSampleName { get; } = new Bindable("rank-impact-pass-ss"); - - [SettingSource("setting")] - public Bindable PlayApplause { get; } = new Bindable(true); - - [SettingSource("setting")] - public BindableDouble ApplauseVolume { get; } = new BindableDouble(0.8) - { - MinValue = 0, - MaxValue = 1, - Precision = 0.1 - }; - - [SettingSource("setting")] - public BindableDouble ApplauseDelay { get; } = new BindableDouble(545) - { - MinValue = 0, - MaxValue = 10000, - Precision = 1 - }; - - [SettingSource("setting")] - public Bindable ApplauseGradeDSampleName { get; } = new Bindable("applause-d"); - - [SettingSource("setting")] - public Bindable ApplauseGradeCSampleName { get; } = new Bindable("applause-c"); - - [SettingSource("setting")] - public Bindable ApplauseGradeBSampleName { get; } = new Bindable("applause-b"); - - [SettingSource("setting")] - public Bindable ApplauseGradeASampleName { get; } = new Bindable("applause-a"); - - [SettingSource("setting")] - public Bindable ApplauseGradeSSampleName { get; } = new Bindable("applause-s"); - - [SettingSource("setting")] - public Bindable ApplauseGradeSSSampleName { get; } = new Bindable("applause-s"); - - [SettingSource("setting")] - public BindableDouble TickPitchFactor { get; } = new BindableDouble(1) - { - MinValue = 0, - MaxValue = 3, - Precision = 0.1 - }; - - [SettingSource("setting")] - public BindableDouble TickDebounceStart { get; } = new BindableDouble(18) - { - MinValue = 1, - MaxValue = 100 - }; - - [SettingSource("setting")] - public BindableDouble TickDebounceEnd { get; } = new BindableDouble(300) - { - MinValue = 100, - MaxValue = 1000 - }; - - [SettingSource("setting")] - public BindableDouble SwooshPreDelay { get; } = new BindableDouble(443) - { - MinValue = -1000, - MaxValue = 1000 - }; - - [SettingSource("setting")] - public Bindable TickRateEasing { get; } = new Bindable(Easing.OutSine); - - [SettingSource("setting")] - public Bindable TickPitchEasing { get; } = new Bindable(Easing.OutSine); - - [SettingSource("setting")] - public Bindable TickVolumeEasing { get; } = new Bindable(Easing.OutSine); - - [SettingSource("setting")] - public BindableDouble TickVolumeStart { get; } = new BindableDouble(0.6) - { - MinValue = 0, - MaxValue = 1, - Precision = 0.1 - }; - - [SettingSource("setting")] - public BindableDouble TickVolumeEnd { get; } = new BindableDouble(1.0) - { - MinValue = 0, - MaxValue = 1, - Precision = 0.1 - }; - - [SettingSource("setting")] - public BindableDouble ImpactVolume { get; } = new BindableDouble(1.0) - { - MinValue = 0, - MaxValue = 1, - Precision = 0.1 - }; - - [SettingSource("setting")] - public BindableDouble BadgeDinkVolume { get; } = new BindableDouble(1) - { - MinValue = 0, - MaxValue = 1, - Precision = 0.1 - }; - - [SettingSource("setting")] - public BindableDouble SwooshVolume { get; } = new BindableDouble(0.4) - { - MinValue = 0, - MaxValue = 1, - Precision = 0.1 - }; - } } diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index a17a052b97..a745a65103 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -7,6 +7,7 @@ using JetBrains.Annotations; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; @@ -58,6 +59,60 @@ namespace osu.Game.Skinning switch (component) { + case GameplaySkinComponent sample: + switch (sample.Component) + { + case GameplaySkinSamples.ResultScoreTick: + return new DrawableSample(GetSample(new SampleInfo("Results/score-tick"))); + + case GameplaySkinSamples.ResultBadgeTick: + return new DrawableSample(GetSample(new SampleInfo("Results/badge-dink"))); + + case GameplaySkinSamples.ResultBadgeTickMax: + return new DrawableSample(GetSample(new SampleInfo("Results/badge-dink-max"))); + + case GameplaySkinSamples.ResultSwooshUp: + return new DrawableSample(GetSample(new SampleInfo("Results/swoosh-up"))); + + case GameplaySkinSamples.ResultRank_D: + return new DrawableSample(GetSample(new SampleInfo("Results/rank-impact-fail-d"))); + + case GameplaySkinSamples.ResultRank_B: + return new DrawableSample(GetSample(new SampleInfo("Results/rank-impact-fail"))); + + case GameplaySkinSamples.ResultRank_C: + return new DrawableSample(GetSample(new SampleInfo("Results/rank-impact-fail"))); + + case GameplaySkinSamples.ResultRank_A: + return new DrawableSample(GetSample(new SampleInfo("Results/rank-impact-pass"))); + + case GameplaySkinSamples.ResultRank_S: + return new DrawableSample(GetSample(new SampleInfo("Results/rank-impact-pass"))); + + case GameplaySkinSamples.ResultRank_SS: + return new DrawableSample(GetSample(new SampleInfo("Results/rank-impact-pass-ss"))); + + case GameplaySkinSamples.ResultApplause_D: + return new DrawableSample(GetSample(new SampleInfo("Results/applause-d"))); + + case GameplaySkinSamples.ResultApplause_B: + return new DrawableSample(GetSample(new SampleInfo("Results/applause-b"))); + + case GameplaySkinSamples.ResultApplause_C: + return new DrawableSample(GetSample(new SampleInfo("Results/applause-c"))); + + case GameplaySkinSamples.ResultApplause_A: + return new DrawableSample(GetSample(new SampleInfo("Results/applause-a"))); + + case GameplaySkinSamples.ResultApplause_S: + return new DrawableSample(GetSample(new SampleInfo("Results/applause-s"))); + + case GameplaySkinSamples.ResultApplause_SS: + return new DrawableSample(GetSample(new SampleInfo("Results/applause-s"))); + } + + break; + case SkinnableTargetComponent target: switch (target.Target) { diff --git a/osu.Game/Skinning/GameplaySkinSamples.cs b/osu.Game/Skinning/GameplaySkinSamples.cs new file mode 100644 index 0000000000..895e95e0a9 --- /dev/null +++ b/osu.Game/Skinning/GameplaySkinSamples.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. + +namespace osu.Game.Skinning +{ + public enum GameplaySkinSamples + { + // legacy + Applause, + + // results screen + ResultScoreTick, + ResultBadgeTick, + ResultBadgeTickMax, + ResultSwooshUp, + ResultRank_D, + ResultRank_B, + ResultRank_C, + ResultRank_A, + ResultRank_S, + ResultRank_SS, + ResultApplause_D, + ResultApplause_B, + ResultApplause_C, + ResultApplause_A, + ResultApplause_S, + ResultApplause_SS + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 98cc5c8fd8..a484516217 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -10,6 +10,7 @@ using JetBrains.Annotations; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; @@ -395,6 +396,43 @@ namespace osu.Game.Skinning return null; + case GameplaySkinComponent sampleComponent: + var applause = GetSample(new SampleInfo("applause")); + + switch (sampleComponent.Component) + { + case GameplaySkinSamples.Applause: + if (applause != null) + return new DrawableSample(applause); + + break; + + case GameplaySkinSamples.ResultScoreTick: + case GameplaySkinSamples.ResultBadgeTick: + case GameplaySkinSamples.ResultBadgeTickMax: + case GameplaySkinSamples.ResultSwooshUp: + case GameplaySkinSamples.ResultRank_D: + case GameplaySkinSamples.ResultRank_B: + case GameplaySkinSamples.ResultRank_C: + case GameplaySkinSamples.ResultRank_A: + case GameplaySkinSamples.ResultRank_S: + case GameplaySkinSamples.ResultRank_SS: + case GameplaySkinSamples.ResultApplause_D: + case GameplaySkinSamples.ResultApplause_B: + case GameplaySkinSamples.ResultApplause_C: + case GameplaySkinSamples.ResultApplause_A: + case GameplaySkinSamples.ResultApplause_S: + case GameplaySkinSamples.ResultApplause_SS: + if (applause != null) + // Legacy skins don't have sounds for the result screen, but may instead have an 'applause' sound. + // This lets a legacy skin's applause sound play instead of result screen sounds (as to not play over each other) + return Drawable.Empty(); + + break; + } + + break; + case HUDSkinComponent hudComponent: { if (!this.HasFont(LegacyFont.Score)) From ed012a724b8480f4d23530aa9c67c2ef37ce41f0 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 2 Jun 2021 12:49:00 +0900 Subject: [PATCH 195/520] refactor from using public variables --- .../Expanded/Accuracy/AccuracyCircle.cs | 89 +++++++++---------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 5cf41513c8..8060b28d50 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Utils; using osu.Game.Graphics; @@ -77,26 +76,33 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy /// public static readonly Easing ACCURACY_TRANSFORM_EASING = Easing.OutPow10; - // audio sfx parameters - public bool PlayTicks = true; - public bool PlayBadgeSounds = true; - public bool PlaySwooshSound = true; - public bool PlayImpact = true; - public bool PlayApplause = true; - public double ApplauseVolume = 0.8f; - public double ApplauseDelay = 545f; - public double TickPitchFactor = 1f; - public double TickDebounceStart = 18f; - public double TickDebounceEnd = 300f; - public double SwooshPreDelay = 443f; - public Easing TickRateEasing = Easing.OutSine; - public Easing TickPitchEasing = Easing.OutSine; - public Easing TickVolumeEasing = Easing.OutSine; - public double TickVolumeStart = 0.6f; - public double TickVolumeEnd = 1.0f; - public double ImpactVolume = 1.0f; - public double BadgeDinkVolume = 1f; - public double SwooshVolume = 0.4f; + #region Sound Effect Playback Parameters + + // swoosh-up + private const double sfx_swoosh_pre_delay = 443f; + private const double sfx_swoosh_volume = 0.4f; + + // score ticks + private const double sfx_score_tick_debounce_rate_start = 18f; + private const double sfx_score_tick_debounce_rate_end = 300f; + private const Easing sfx_score_tick_debounce_rate_easing = Easing.OutSine; + private const double sfx_score_tick_volume_start = 0.6f; + private const double sfx_score_tick_volume_end = 1.0f; + private const Easing sfx_score_tick_volume_easing = Easing.OutSine; + private const Easing sfx_score_tick_pitch_easing = Easing.OutSine; + + // badge dinks + private const double sfx_badge_dink_volume = 1f; + + // impact + private const double sfx_rank_impact_volume = 1.0f; + + // applause + private const bool sfx_applause_enabled = true; + private const double sfx_applause_pre_delay = 545f; + private const double sfx_applause_volume = 0.8f; + + #endregion private readonly ScoreInfo score; @@ -126,8 +132,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private double lastTickPlaybackTime; private bool isTicking; - private AudioManager audioManager; - private readonly bool withFlair; public AccuracyCircle(ScoreInfo score, bool withFlair) @@ -137,10 +141,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } [BackgroundDependencyLoader] - private void load(AudioManager audio, GameHost host, ISkinSource skin) + private void load(GameHost host, ISkinSource skin) { - audioManager = audio; - InternalChildren = new Drawable[] { new SmoothCircularProgress @@ -261,7 +263,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (withFlair) { - tickPlaybackRate = new Bindable(TickDebounceStart); + tickPlaybackRate = new Bindable(sfx_score_tick_debounce_rate_start); AddRangeInternal(new Drawable[] { @@ -297,7 +299,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { base.Update(); - if (!PlayTicks || !isTicking) return; + if (!isTicking) return; bool enoughTimePassedSinceLastPlayback = Clock.CurrentTime - lastTickPlaybackTime >= tickPlaybackRate.Value; @@ -313,11 +315,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint); - if (PlaySwooshSound && swooshUpSound != null) + if (swooshUpSound != null) { - this.Delay(SwooshPreDelay).Schedule(() => + this.Delay(sfx_swoosh_pre_delay).Schedule(() => { - swooshUpSound.Volume.Value = SwooshVolume; + swooshUpSound.VolumeTo(sfx_swoosh_volume); swooshUpSound.Play(); }); } @@ -333,14 +335,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Schedule(() => { - if (!PlayTicks) return; - if (scoreTickSound != null) { // doesn't work - scoreTickSound.FrequencyTo(1).Then().FrequencyTo(1 + targetAccuracy * TickPitchFactor, ACCURACY_TRANSFORM_DURATION, TickPitchEasing); - scoreTickSound.VolumeTo(TickVolumeStart).Then().VolumeTo(TickVolumeEnd, ACCURACY_TRANSFORM_DURATION, TickVolumeEasing); - this.TransformBindableTo(tickPlaybackRate, TickDebounceEnd, ACCURACY_TRANSFORM_DURATION, TickRateEasing); + scoreTickSound.FrequencyTo(1).Then().FrequencyTo(1 + targetAccuracy, ACCURACY_TRANSFORM_DURATION, sfx_score_tick_pitch_easing); + scoreTickSound.VolumeTo(sfx_score_tick_volume_start).Then().VolumeTo(sfx_score_tick_volume_end, ACCURACY_TRANSFORM_DURATION, sfx_score_tick_volume_easing); + this.TransformBindableTo(tickPlaybackRate, sfx_score_tick_debounce_rate_end, ACCURACY_TRANSFORM_DURATION, sfx_score_tick_debounce_rate_easing); } isTicking = true; @@ -357,13 +357,14 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { badge.Appear(); - if (!PlayBadgeSounds) return; - Schedule(() => { DrawableSample dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound; + + if (dink == null) return; + dink.FrequencyTo(1 + badgeNum++ * 0.05); - dink.VolumeTo(BadgeDinkVolume); + dink.VolumeTo(sfx_badge_dink_volume); dink.Play(); }); } @@ -377,8 +378,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { isTicking = false; - if (!PlayImpact) return; - DrawableSample impact = null; switch (score.Rank) @@ -412,13 +411,13 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (impact == null) return; - impact.Volume.Value = ImpactVolume; + impact.VolumeTo(sfx_rank_impact_volume); impact.Play(); }); - using (BeginDelayedSequence(ApplauseDelay)) + using (BeginDelayedSequence(sfx_applause_pre_delay)) { - if (!PlayApplause) return; + if (!sfx_applause_enabled) return; Schedule(() => { @@ -455,7 +454,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (applause == null) return; - applause.Volume.Value = ApplauseVolume; + applause.VolumeTo(sfx_applause_volume); applause.Play(); }); } From 582360d0c80d9e1f9d111b51acf847c6de5c4e53 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 2 Jun 2021 16:57:09 +0900 Subject: [PATCH 196/520] only load the required impact/applause samples --- .../Expanded/Accuracy/AccuracyCircle.cs | 188 +++++++----------- 1 file changed, 67 insertions(+), 121 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 8060b28d50..ac5c8dbed8 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -98,7 +98,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private const double sfx_rank_impact_volume = 1.0f; // applause - private const bool sfx_applause_enabled = true; private const double sfx_applause_pre_delay = 545f; private const double sfx_applause_volume = 0.8f; @@ -115,29 +114,19 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private DrawableSample badgeTickSound; private DrawableSample badgeMaxSound; private DrawableSample swooshUpSound; - private DrawableSample rankDImpactSound; - private DrawableSample rankBImpactSound; - private DrawableSample rankCImpactSound; - private DrawableSample rankAImpactSound; - private DrawableSample rankSImpactSound; - private DrawableSample rankSSImpactSound; - private DrawableSample rankDApplauseSound; - private DrawableSample rankBApplauseSound; - private DrawableSample rankCApplauseSound; - private DrawableSample rankAApplauseSound; - private DrawableSample rankSApplauseSound; - private DrawableSample rankSSApplauseSound; + private DrawableSample rankImpactSound; + private DrawableSample rankApplauseSound; private Bindable tickPlaybackRate = new Bindable(); private double lastTickPlaybackTime; private bool isTicking; - private readonly bool withFlair; + private readonly bool sfxEnabled; - public AccuracyCircle(ScoreInfo score, bool withFlair) + public AccuracyCircle(ScoreInfo score, bool sfxEnabled = false) { this.score = score; - this.withFlair = withFlair; + this.sfxEnabled = sfxEnabled; } [BackgroundDependencyLoader] @@ -261,28 +250,53 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy rankText = new RankText(score.Rank) }; - if (withFlair) + if (sfxEnabled) { tickPlaybackRate = new Bindable(sfx_score_tick_debounce_rate_start); + switch (score.Rank) + { + case ScoreRank.D: + rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_D)) as DrawableSample; + rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_D)) as DrawableSample; + break; + + case ScoreRank.C: + rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_C)) as DrawableSample; + rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_C)) as DrawableSample; + break; + + case ScoreRank.B: + rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_B)) as DrawableSample; + rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_B)) as DrawableSample; + break; + + case ScoreRank.A: + rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_A)) as DrawableSample; + rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_A)) as DrawableSample; + break; + + case ScoreRank.S: + case ScoreRank.SH: + rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_S)) as DrawableSample; + rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_S)) as DrawableSample; + break; + + case ScoreRank.X: + case ScoreRank.XH: + rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_SS)) as DrawableSample; + rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_SS)) as DrawableSample; + break; + } + AddRangeInternal(new Drawable[] { + rankImpactSound, + rankApplauseSound, scoreTickSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultScoreTick)) as DrawableSample, badgeTickSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultBadgeTick)) as DrawableSample, badgeMaxSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultBadgeTickMax)) as DrawableSample, - swooshUpSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultSwooshUp)) as DrawableSample, - rankDImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_D)) as DrawableSample, - rankBImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_B)) as DrawableSample, - rankCImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_C)) as DrawableSample, - rankAImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_A)) as DrawableSample, - rankSImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_S)) as DrawableSample, - rankSSImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_SS)) as DrawableSample, - rankDApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_D)) as DrawableSample, - rankBApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_B)) as DrawableSample, - rankCApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_C)) as DrawableSample, - rankAApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_A)) as DrawableSample, - rankSApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_S)) as DrawableSample, - rankSSApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_SS)) as DrawableSample + swooshUpSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultSwooshUp)) as DrawableSample }); } } @@ -315,7 +329,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint); - if (swooshUpSound != null) + if (sfxEnabled) { this.Delay(sfx_swoosh_pre_delay).Schedule(() => { @@ -333,18 +347,17 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); - Schedule(() => + if (sfxEnabled) { - if (scoreTickSound != null) + Schedule(() => { - // doesn't work - scoreTickSound.FrequencyTo(1).Then().FrequencyTo(1 + targetAccuracy, ACCURACY_TRANSFORM_DURATION, sfx_score_tick_pitch_easing); + scoreTickSound.FrequencyTo(1 + targetAccuracy, ACCURACY_TRANSFORM_DURATION, sfx_score_tick_pitch_easing); scoreTickSound.VolumeTo(sfx_score_tick_volume_start).Then().VolumeTo(sfx_score_tick_volume_end, ACCURACY_TRANSFORM_DURATION, sfx_score_tick_volume_easing); this.TransformBindableTo(tickPlaybackRate, sfx_score_tick_debounce_rate_end, ACCURACY_TRANSFORM_DURATION, sfx_score_tick_debounce_rate_easing); - } - isTicking = true; - }); + isTicking = true; + }); + } int badgeNum = 0; @@ -353,20 +366,20 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (badge.Accuracy > score.Accuracy) continue; - using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(1 - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION, true)) + using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(1 - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION)) { badge.Appear(); - Schedule(() => + if (sfxEnabled) { - DrawableSample dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound; - - if (dink == null) return; - - dink.FrequencyTo(1 + badgeNum++ * 0.05); - dink.VolumeTo(sfx_badge_dink_volume); - dink.Play(); - }); + Schedule(() => + { + DrawableSample dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound; + dink.FrequencyTo(1 + badgeNum++ * 0.05); + dink.VolumeTo(sfx_badge_dink_volume); + dink.Play(); + }); + } } } @@ -374,88 +387,21 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { rankText.Appear(); + if (!sfxEnabled) return; + Schedule(() => { isTicking = false; - - DrawableSample impact = null; - - switch (score.Rank) - { - case ScoreRank.D: - impact = rankDImpactSound; - break; - - case ScoreRank.C: - impact = rankCImpactSound; - break; - - case ScoreRank.B: - impact = rankBImpactSound; - break; - - case ScoreRank.A: - impact = rankAImpactSound; - break; - - case ScoreRank.S: - case ScoreRank.SH: - impact = rankSImpactSound; - break; - - case ScoreRank.X: - case ScoreRank.XH: - impact = rankSSImpactSound; - break; - } - - if (impact == null) return; - - impact.VolumeTo(sfx_rank_impact_volume); - impact.Play(); + rankImpactSound.VolumeTo(sfx_rank_impact_volume); + rankImpactSound.Play(); }); using (BeginDelayedSequence(sfx_applause_pre_delay)) { - if (!sfx_applause_enabled) return; - Schedule(() => { - DrawableSample applause = null; - - switch (score.Rank) - { - case ScoreRank.D: - applause = rankDApplauseSound; - break; - - case ScoreRank.C: - applause = rankCApplauseSound; - break; - - case ScoreRank.B: - applause = rankBApplauseSound; - break; - - case ScoreRank.A: - applause = rankAApplauseSound; - break; - - case ScoreRank.S: - case ScoreRank.SH: - applause = rankSApplauseSound; - break; - - case ScoreRank.X: - case ScoreRank.XH: - applause = rankSSApplauseSound; - break; - } - - if (applause == null) return; - - applause.VolumeTo(sfx_applause_volume); - applause.Play(); + rankApplauseSound.VolumeTo(sfx_applause_volume); + rankApplauseSound.Play(); }); } } From 054de675ff1979af4923658c0b0b3beb19a8a054 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 3 Jun 2021 15:31:34 +0900 Subject: [PATCH 197/520] allow skinned 'applause' sample to override results screen sfx --- .../Expanded/Accuracy/AccuracyCircle.cs | 106 ++++++++++-------- 1 file changed, 61 insertions(+), 45 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index ac5c8dbed8..af43477e84 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -116,12 +116,14 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private DrawableSample swooshUpSound; private DrawableSample rankImpactSound; private DrawableSample rankApplauseSound; + private DrawableSample legacySkinApplauseSound; private Bindable tickPlaybackRate = new Bindable(); private double lastTickPlaybackTime; private bool isTicking; private readonly bool sfxEnabled; + private bool legacySkin => legacySkinApplauseSound != null; public AccuracyCircle(ScoreInfo score, bool sfxEnabled = false) { @@ -252,52 +254,62 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (sfxEnabled) { - tickPlaybackRate = new Bindable(sfx_score_tick_debounce_rate_start); + Drawable legacySkinApplause = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.Applause)); - switch (score.Rank) + if (legacySkinApplause != null) { - case ScoreRank.D: - rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_D)) as DrawableSample; - rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_D)) as DrawableSample; - break; - - case ScoreRank.C: - rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_C)) as DrawableSample; - rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_C)) as DrawableSample; - break; - - case ScoreRank.B: - rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_B)) as DrawableSample; - rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_B)) as DrawableSample; - break; - - case ScoreRank.A: - rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_A)) as DrawableSample; - rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_A)) as DrawableSample; - break; - - case ScoreRank.S: - case ScoreRank.SH: - rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_S)) as DrawableSample; - rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_S)) as DrawableSample; - break; - - case ScoreRank.X: - case ScoreRank.XH: - rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_SS)) as DrawableSample; - rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_SS)) as DrawableSample; - break; + AddInternal(legacySkinApplause); + legacySkinApplauseSound = legacySkinApplause as DrawableSample; } - - AddRangeInternal(new Drawable[] + else { - rankImpactSound, - rankApplauseSound, - scoreTickSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultScoreTick)) as DrawableSample, - badgeTickSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultBadgeTick)) as DrawableSample, - badgeMaxSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultBadgeTickMax)) as DrawableSample, - swooshUpSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultSwooshUp)) as DrawableSample - }); + tickPlaybackRate = new Bindable(sfx_score_tick_debounce_rate_start); + + switch (score.Rank) + { + case ScoreRank.D: + rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_D)) as DrawableSample; + rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_D)) as DrawableSample; + break; + + case ScoreRank.C: + rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_C)) as DrawableSample; + rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_C)) as DrawableSample; + break; + + case ScoreRank.B: + rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_B)) as DrawableSample; + rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_B)) as DrawableSample; + break; + + case ScoreRank.A: + rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_A)) as DrawableSample; + rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_A)) as DrawableSample; + break; + + case ScoreRank.S: + case ScoreRank.SH: + rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_S)) as DrawableSample; + rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_S)) as DrawableSample; + break; + + case ScoreRank.X: + case ScoreRank.XH: + rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_SS)) as DrawableSample; + rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_SS)) as DrawableSample; + break; + } + + AddRangeInternal(new Drawable[] + { + rankImpactSound, + rankApplauseSound, + scoreTickSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultScoreTick)) as DrawableSample, + badgeTickSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultBadgeTick)) as DrawableSample, + badgeMaxSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultBadgeTickMax)) as DrawableSample, + swooshUpSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultSwooshUp)) as DrawableSample + }); + } } } @@ -329,7 +341,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint); - if (sfxEnabled) + if (sfxEnabled && !legacySkin) { this.Delay(sfx_swoosh_pre_delay).Schedule(() => { @@ -347,7 +359,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); - if (sfxEnabled) + if (sfxEnabled && !legacySkin) { Schedule(() => { @@ -370,7 +382,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { badge.Appear(); - if (sfxEnabled) + if (sfxEnabled && !legacySkin) { Schedule(() => { @@ -389,6 +401,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (!sfxEnabled) return; + legacySkinApplauseSound?.Play(); + + if (legacySkin) return; + Schedule(() => { isTicking = false; From 49fbd35e91a1c64b1ebb988590fee7bc26e788ef Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 4 Jun 2021 11:58:11 +0900 Subject: [PATCH 198/520] remove sound design tool --- .../SoundDesign/TestSceneAccuracyCircle.cs | 1129 ----------------- 1 file changed, 1129 deletions(-) delete mode 100644 osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs diff --git a/osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs deleted file mode 100644 index e630bb5983..0000000000 --- a/osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs +++ /dev/null @@ -1,1129 +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 System.Collections.Generic; -using System.IO; -using Newtonsoft.Json; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Platform; -using osu.Game.Configuration; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Overlays; -using osu.Game.Overlays.Settings; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osu.Game.Screens.Ranking.Expanded.Accuracy; -using osu.Game.Tests.Beatmaps; -using osu.Game.Users; -using osuTK; - -namespace osu.Game.Tests.Visual.SoundDesign -{ - public class TestSceneAccuracyCircle : OsuTestScene - { - [Resolved] - private AudioManager audioManager { get; set; } - - [Resolved] - private OsuColour colours { get; set; } - - private DrawableSample previewSampleChannel; - private AccuracyCircleAudioSettings settings = new AccuracyCircleAudioSettings(); - private OsuTextBox saveFilename; - - private Storage presetStorage; - private FileSelector presetFileSelector; - - private Bindable sampleLoadTarget = new Bindable(); - private Bindable selectedSampleName = new Bindable(); - - private Container accuracyCircle; - - private enum SampleLoadTarget - { - ScoreTick, - BadgeDink, - BadgeDinkMax, - Swoosh, - ImpactD, - ImpactC, - ImpactB, - ImpactA, - ImpactS, - ImpactSS, - ApplauseD, - ApplauseC, - ApplauseB, - ApplauseA, - ApplauseS, - ApplauseSS, - }; - - private enum SectionTabs - { - [System.ComponentModel.Description("Score Ticks")] - ScoreTicks, - - [System.ComponentModel.Description("Badge Dinks")] - BadgeDinks, - - [System.ComponentModel.Description("Swoosh")] - Swoosh, - - [System.ComponentModel.Description("Impact")] - Impact, - - [System.ComponentModel.Description("Applause")] - Applause, - - [System.ComponentModel.Description("Preset")] - Preset - } - - private OsuTabControl tabSelector; - - private Dictionary tabContainers = new Dictionary(); - private FillFlowContainer sampleSelectContainer; - - private FileSelector sampleFileSelector; - - [BackgroundDependencyLoader] - private void load(GameHost host) - { - presetStorage = host.Storage.GetStorageForDirectory("presets"); - - Children = new Drawable[] - { - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Width = 0.5f, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("222") - }, - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Padding = new MarginPadding(10), - Children = new Drawable[] - { - tabSelector = new OsuTabControl - { - RelativeSizeAxes = Axes.X, - Width = 1f, - Height = 24, - }, - - #region score ticks - - // ==================== SCORE TICKS ==================== - tabContainers[SectionTabs.ScoreTicks] = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Width = 1f, - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = "Play Ticks", - Current = { BindTarget = settings.PlayTicks } - }, - new SettingsSlider - { - LabelText = "Tick Volume (Start)", - Current = { BindTarget = settings.TickVolumeStart } - }, - new SettingsSlider - { - LabelText = "Tick Volume (End)", - Current = { BindTarget = settings.TickVolumeEnd } - }, - new SettingsSlider - { - LabelText = "ScoreTick Start Debounce Rate", - Current = { BindTarget = settings.TickDebounceStart } - }, - new SettingsSlider - { - LabelText = "ScoreTick End Debounce Rate", - Current = { BindTarget = settings.TickDebounceEnd } - }, - new OsuSpriteText - { - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - Text = "ScoreTick Rate Easing:" - }, - new SettingsEnumDropdown - { - Current = { BindTarget = settings.TickRateEasing } - }, - new SettingsSlider - { - LabelText = "ScoreTick Pitch Factor", - Current = { BindTarget = settings.TickPitchFactor } - }, - new OsuSpriteText - { - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - Text = "Pitch Easing:" - }, - new SettingsEnumDropdown - { - Current = { BindTarget = settings.TickPitchEasing } - }, - new OsuSpriteText - { - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - Text = "Volume Easing:" - }, - new SettingsEnumDropdown - { - Current = { BindTarget = settings.TickVolumeEasing } - }, - new OsuSpriteText - { - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - Text = "Tick Sample:" - }, - new OsuSpriteText - { - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2 }, - Current = { BindTarget = settings.TickSampleName } - } - } - }, - - #endregion - - #region badge dinks - - // ==================== BADGE DINKS ==================== - tabContainers[SectionTabs.BadgeDinks] = new FillFlowContainer - { - Alpha = 0, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Width = 1f, - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = "Play BadgeSounds", - Current = { BindTarget = settings.PlayBadgeSounds } - }, - new SettingsSlider - { - LabelText = "Badge Dink Volume", - Current = { BindTarget = settings.BadgeDinkVolume } - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Badge Dink Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.BadgeSampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Badge Max Dink Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.BadgeMaxSampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - } - } - }, - - #endregion - - #region swoosh - - // ==================== SWOOSHES ==================== - tabContainers[SectionTabs.Swoosh] = new FillFlowContainer - { - Alpha = 0, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Width = 1f, - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = "Play Swoosh", - Current = { BindTarget = settings.PlaySwooshSound } - }, - new SettingsSlider - { - LabelText = "Swoosh Volume", - Current = { BindTarget = settings.SwooshVolume } - }, - new SettingsSlider - { - LabelText = "Swoosh Pre-Delay (ms)", - Current = { BindTarget = settings.SwooshPreDelay } - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Swoosh Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.SwooshSampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - } - } - }, - - #endregion - - #region impact - - // ==================== IMPACT ==================== - tabContainers[SectionTabs.Impact] = new FillFlowContainer - { - Alpha = 0, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Width = 1f, - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = "Play Impact", - Current = { BindTarget = settings.PlayImpact } - }, - new SettingsSlider - { - LabelText = "Impact Volume", - Current = { BindTarget = settings.ImpactVolume } - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Grade D Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.ImpactGradeDSampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Grade C Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.ImpactGradeCSampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Grade B Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.ImpactGradeBSampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Grade A Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.ImpactGradeASampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Grade S Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.ImpactGradeSSampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Grade SS Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.ImpactGradeSSSampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - } - } - }, - - #endregion - - #region applause - - // ==================== APPLAUSE ==================== - tabContainers[SectionTabs.Applause] = new FillFlowContainer - { - Alpha = 0, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Width = 1f, - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = "Play Applause", - Current = { BindTarget = settings.PlayApplause } - }, - new SettingsSlider - { - LabelText = "Applause Volume", - Current = { BindTarget = settings.ApplauseVolume } - }, - new SettingsSlider - { - LabelText = "Applause Delay (ms)", - Current = { BindTarget = settings.ApplauseDelay } - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Grade D Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.ApplauseGradeDSampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Grade C Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.ApplauseGradeCSampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Grade B Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.ApplauseGradeBSampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Grade A Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.ApplauseGradeASampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Grade S Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.ApplauseGradeSSampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(), - Text = "Grade SS Sample:", - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Current = { BindTarget = settings.ApplauseGradeSSSampleName }, - Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, - } - } - }, - - #endregion - - #region preset - - // ==================== PRESET ==================== - tabContainers[SectionTabs.Preset] = new FillFlowContainer - { - Alpha = 0, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Width = 1f, - Children = new Drawable[] - { - new OsuSpriteText - { - Font = OsuFont.Default.With(size: 24), - Text = "Load", - Colour = colours.Yellow - }, - presetFileSelector = new FileSelector(presetStorage.GetFullPath(string.Empty)) - { - RelativeSizeAxes = Axes.X, - Height = 300, - }, - new OsuSpriteText - { - Font = OsuFont.Default.With(size: 24), - Text = "Save", - Colour = colours.Yellow - }, - saveFilename = new OsuTextBox - { - PlaceholderText = "New preset filename", - RelativeSizeAxes = Axes.X, - }, - new TriangleButton - { - Text = "Save", - Action = savePreset, - RelativeSizeAxes = Axes.X, - }, - } - }, - - #endregion - - #region fileselector - - // ==================== SAMPLE SELECTOR ==================== - sampleSelectContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Padding = new MarginPadding(10) - { - Top = 20, - }, - Width = 1f, - Children = new Drawable[] - { - new OsuSpriteText - { - Font = OsuFont.Default.With(size: 20), - Text = "Load Sample", - Colour = colours.Yellow - }, - sampleFileSelector = new FileSelector("/Users/jamie/Sandbox/derp/Samples/Results") - { - RelativeSizeAxes = Axes.X, - Height = 300, - }, - new TriangleButton - { - Text = "Refresh", - Action = refreshSampleBrowser, - RelativeSizeAxes = Axes.X, - }, - new SettingsEnumDropdown - { - Current = { BindTarget = sampleLoadTarget } - }, - new TriangleButton - { - Text = "Load Sample", - Action = loadSample, - RelativeSizeAxes = Axes.X, - } - } - } - - #endregion - } - } - } - } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Width = 0.5f, - Children = new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#555"), Color4Extensions.FromHex("#333")) - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Children = new[] - { - new TriangleButton - { - Text = "Low D Rank", - Action = CreateLowRankDCircle, - RelativeSizeAxes = Axes.X, - Width = 0.25f, - }, - new TriangleButton - { - Text = "D Rank", - Action = CreateDRankCircle, - RelativeSizeAxes = Axes.X, - Width = 0.25f, - }, - new TriangleButton - { - Text = "C Rank", - Action = CreateCRankCircle, - RelativeSizeAxes = Axes.X, - Width = 0.25f, - }, - new TriangleButton - { - Text = "B Rank", - Action = CreateBRankCircle, - RelativeSizeAxes = Axes.X, - Width = 0.25f, - }, - new TriangleButton - { - Text = "A Rank", - Action = CreateARankCircle, - RelativeSizeAxes = Axes.X, - Width = 0.25f, - }, - new TriangleButton - { - Text = "S Rank", - Action = CreateSRankCircle, - RelativeSizeAxes = Axes.X, - Width = 0.25f, - }, - new TriangleButton - { - Text = "Almost SS Rank", - Action = CreateAlmostSSRankCircle, - RelativeSizeAxes = Axes.X, - Width = 0.25f, - }, - new TriangleButton - { - Text = "SS Rank", - Action = CreateSSRankCircle, - RelativeSizeAxes = Axes.X, - Width = 0.25f, - }, - } - }, - accuracyCircle = new Container - { - RelativeSizeAxes = Axes.Both, - // Child = CreateRankDCircle() - } - } - } - } - }, - }; - - presetFileSelector.CurrentFile.ValueChanged += value => - { - string path = value.NewValue.FullName; - - loadPreset(path); - saveFilename.Text = Path.GetFileNameWithoutExtension(path); - }; - - sampleFileSelector.CurrentFile.ValueChanged += value => - { - var sample = Path.GetFileNameWithoutExtension(value.NewValue.Name); - - previewSampleChannel?.Dispose(); - previewSampleChannel = new DrawableSample(audioManager.Samples.Get($"Results/{sample}")); - previewSampleChannel?.Play(); - - selectedSampleName.Value = sample; - }; - - tabSelector.Current.ValueChanged += tab => - { - tabContainers[tab.OldValue].Hide(); - tabContainers[tab.NewValue].Show(); - - switch (tab.NewValue) - { - case SectionTabs.Preset: - sampleSelectContainer.Hide(); - break; - - case SectionTabs.Impact: - sampleLoadTarget.Value = SampleLoadTarget.ImpactD; - sampleSelectContainer.Show(); - break; - - case SectionTabs.Swoosh: - sampleLoadTarget.Value = SampleLoadTarget.Swoosh; - sampleSelectContainer.Show(); - break; - - case SectionTabs.BadgeDinks: - sampleLoadTarget.Value = SampleLoadTarget.BadgeDink; - sampleSelectContainer.Show(); - break; - - case SectionTabs.ScoreTicks: - sampleLoadTarget.Value = SampleLoadTarget.ScoreTick; - sampleSelectContainer.Show(); - break; - - case SectionTabs.Applause: - sampleLoadTarget.Value = SampleLoadTarget.ApplauseD; - sampleSelectContainer.Show(); - break; - } - }; - } - - #region rank scenarios - - [Test] - public void TestDoNothing() => AddStep("show", () => - { - /* do nothing */ - }); - - [Test] - public void TestLowDRank() => AddStep("show", CreateLowRankDCircle); - - [Test] - public void TestDRank() => AddStep("show", CreateDRankCircle); - - [Test] - public void TestCRank() => AddStep("show", CreateCRankCircle); - - [Test] - public void TestBRank() => AddStep("show", CreateBRankCircle); - - [Test] - public void TestARank() => AddStep("show", CreateARankCircle); - - [Test] - public void TestSRank() => AddStep("show", CreateSRankCircle); - - [Test] - public void TestAlmostSSRank() => AddStep("show", CreateAlmostSSRankCircle); - - [Test] - public void TestSSRank() => AddStep("show", CreateSSRankCircle); - - #endregion - - public void CreateLowRankDCircle() => - accuracyCircle.Child = CreateAccuracyCircle(createScore(0.2, ScoreRank.D)); - - public void CreateDRankCircle() => - accuracyCircle.Child = CreateAccuracyCircle(createScore(0.5, ScoreRank.D)); - - public void CreateCRankCircle() => - accuracyCircle.Child = CreateAccuracyCircle(createScore(0.75, ScoreRank.C)); - - public void CreateBRankCircle() => - accuracyCircle.Child = CreateAccuracyCircle(createScore(0.85, ScoreRank.B)); - - public void CreateARankCircle() => - accuracyCircle.Child = CreateAccuracyCircle(createScore(0.925, ScoreRank.A)); - - public void CreateSRankCircle() => - accuracyCircle.Child = CreateAccuracyCircle(createScore(0.975, ScoreRank.S)); - - public void CreateAlmostSSRankCircle() => - accuracyCircle.Child = CreateAccuracyCircle(createScore(0.9999, ScoreRank.S)); - - public void CreateSSRankCircle() => - accuracyCircle.Child = CreateAccuracyCircle(createScore(1, ScoreRank.X)); - - public AccuracyCircle CreateAccuracyCircle(ScoreInfo score) - { - var newAccuracyCircle = new AccuracyCircle(score, true) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(230), - }; - - // newAccuracyCircle.BindAudioSettings(settings); - - return newAccuracyCircle; - } - - private void savePreset() - { - string path = presetStorage.GetFullPath($"{saveFilename.Text}.json", true); - File.WriteAllText(path, JsonConvert.SerializeObject(settings)); - presetFileSelector.CurrentFile.Value = new FileInfo(path); - } - - private void loadPreset(string filename) - { - var saved = JsonConvert.DeserializeObject(File.ReadAllText(presetStorage.GetFullPath(filename))); - - foreach (var (_, prop) in saved.GetSettingsSourceProperties()) - { - var targetBindable = (IBindable)prop.GetValue(settings); - var sourceBindable = (IBindable)prop.GetValue(saved); - - ((IParseable)targetBindable)?.Parse(sourceBindable); - } - } - - private void refreshSampleBrowser() => - sampleFileSelector.CurrentPath.Value = new DirectoryInfo(sampleFileSelector.CurrentPath.Value.FullName); - - private void loadSample() - { - switch (sampleLoadTarget.Value) - { - case SampleLoadTarget.Swoosh: - settings.SwooshSampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.ScoreTick: - settings.TickSampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.BadgeDink: - settings.BadgeSampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.BadgeDinkMax: - settings.BadgeMaxSampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.ImpactD: - settings.ImpactGradeDSampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.ImpactC: - settings.ImpactGradeCSampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.ImpactB: - settings.ImpactGradeBSampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.ImpactA: - settings.ImpactGradeASampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.ImpactS: - settings.ImpactGradeSSampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.ImpactSS: - settings.ImpactGradeSSSampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.ApplauseD: - settings.ApplauseGradeDSampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.ApplauseC: - settings.ApplauseGradeCSampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.ApplauseB: - settings.ApplauseGradeBSampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.ApplauseA: - settings.ApplauseGradeASampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.ApplauseS: - settings.ApplauseGradeSSampleName.Value = selectedSampleName.Value; - break; - - case SampleLoadTarget.ApplauseSS: - settings.ApplauseGradeSSSampleName.Value = selectedSampleName.Value; - break; - } - } - - private ScoreInfo createScore(double accuracy = 0.95, ScoreRank rank = ScoreRank.S) => new ScoreInfo - { - User = new User - { - Id = 2, - Username = "peppy", - }, - Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, - Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, - TotalScore = 2845370, - Accuracy = accuracy, - MaxCombo = 999, - Rank = rank, - Date = DateTimeOffset.Now, - Statistics = - { - { HitResult.Miss, 1 }, - { HitResult.Meh, 50 }, - { HitResult.Good, 100 }, - { HitResult.Great, 300 }, - } - }; - } - - [Serializable] - public class AccuracyCircleAudioSettings - { - [SettingSource("setting")] - public Bindable PlayTicks { get; } = new Bindable(true); - - [SettingSource("setting")] - public Bindable TickSampleName { get; } = new Bindable("score-tick"); - - [SettingSource("setting")] - public Bindable PlayBadgeSounds { get; } = new Bindable(true); - - [SettingSource("setting")] - public Bindable BadgeSampleName { get; } = new Bindable("badge-dink"); - - [SettingSource("setting")] - public Bindable BadgeMaxSampleName { get; } = new Bindable("badge-dink-max"); - - [SettingSource("setting")] - public Bindable PlaySwooshSound { get; } = new Bindable(true); - - [SettingSource("setting")] - public Bindable SwooshSampleName { get; } = new Bindable("swoosh-up"); - - [SettingSource("setting")] - public Bindable PlayImpact { get; } = new Bindable(true); - - [SettingSource("setting")] - public Bindable ImpactGradeDSampleName { get; } = new Bindable("rank-impact-fail-d"); - - [SettingSource("setting")] - public Bindable ImpactGradeCSampleName { get; } = new Bindable("rank-impact-fail"); - - [SettingSource("setting")] - public Bindable ImpactGradeBSampleName { get; } = new Bindable("rank-impact-fail"); - - [SettingSource("setting")] - public Bindable ImpactGradeASampleName { get; } = new Bindable("rank-impact-pass"); - - [SettingSource("setting")] - public Bindable ImpactGradeSSampleName { get; } = new Bindable("rank-impact-pass"); - - [SettingSource("setting")] - public Bindable ImpactGradeSSSampleName { get; } = new Bindable("rank-impact-pass-ss"); - - [SettingSource("setting")] - public Bindable PlayApplause { get; } = new Bindable(true); - - [SettingSource("setting")] - public BindableDouble ApplauseVolume { get; } = new BindableDouble(0.8) - { - MinValue = 0, - MaxValue = 1, - Precision = 0.1 - }; - - [SettingSource("setting")] - public BindableDouble ApplauseDelay { get; } = new BindableDouble(545) - { - MinValue = 0, - MaxValue = 10000, - Precision = 1 - }; - - [SettingSource("setting")] - public Bindable ApplauseGradeDSampleName { get; } = new Bindable("applause-d"); - - [SettingSource("setting")] - public Bindable ApplauseGradeCSampleName { get; } = new Bindable("applause-c"); - - [SettingSource("setting")] - public Bindable ApplauseGradeBSampleName { get; } = new Bindable("applause-b"); - - [SettingSource("setting")] - public Bindable ApplauseGradeASampleName { get; } = new Bindable("applause-a"); - - [SettingSource("setting")] - public Bindable ApplauseGradeSSampleName { get; } = new Bindable("applause-s"); - - [SettingSource("setting")] - public Bindable ApplauseGradeSSSampleName { get; } = new Bindable("applause-s"); - - [SettingSource("setting")] - public BindableDouble TickPitchFactor { get; } = new BindableDouble(1) - { - MinValue = 0, - MaxValue = 3, - Precision = 0.1 - }; - - [SettingSource("setting")] - public BindableDouble TickDebounceStart { get; } = new BindableDouble(18) - { - MinValue = 1, - MaxValue = 100 - }; - - [SettingSource("setting")] - public BindableDouble TickDebounceEnd { get; } = new BindableDouble(300) - { - MinValue = 100, - MaxValue = 1000 - }; - - [SettingSource("setting")] - public BindableDouble SwooshPreDelay { get; } = new BindableDouble(443) - { - MinValue = -1000, - MaxValue = 1000 - }; - - [SettingSource("setting")] - public Bindable TickRateEasing { get; } = new Bindable(Easing.OutSine); - - [SettingSource("setting")] - public Bindable TickPitchEasing { get; } = new Bindable(Easing.OutSine); - - [SettingSource("setting")] - public Bindable TickVolumeEasing { get; } = new Bindable(Easing.OutSine); - - [SettingSource("setting")] - public BindableDouble TickVolumeStart { get; } = new BindableDouble(0.6) - { - MinValue = 0, - MaxValue = 1, - Precision = 0.1 - }; - - [SettingSource("setting")] - public BindableDouble TickVolumeEnd { get; } = new BindableDouble(1.0) - { - MinValue = 0, - MaxValue = 1, - Precision = 0.1 - }; - - [SettingSource("setting")] - public BindableDouble ImpactVolume { get; } = new BindableDouble(1.0) - { - MinValue = 0, - MaxValue = 1, - Precision = 0.1 - }; - - [SettingSource("setting")] - public BindableDouble BadgeDinkVolume { get; } = new BindableDouble(1) - { - MinValue = 0, - MaxValue = 1, - Precision = 0.1 - }; - - [SettingSource("setting")] - public BindableDouble SwooshVolume { get; } = new BindableDouble(0.4) - { - MinValue = 0, - MaxValue = 1, - Precision = 0.1 - }; - } -} From 5fa93661522841c3188a17724518616beed31335 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 16:22:16 +0900 Subject: [PATCH 199/520] Update resources --- 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 3d51357d8b..17b07b4695 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d299ba4fda..7e0fc38f58 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 9e178b267a..41fadb245e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 50819ef91fb84d441d790f27ba3843b66734d73b Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 4 Jun 2021 16:41:52 +0900 Subject: [PATCH 200/520] use a dictionary for sample lookups instead --- osu.Game/Skinning/DefaultSkin.cs | 73 ++++++++++---------------------- osu.Game/Skinning/LegacySkin.cs | 2 +- 2 files changed, 24 insertions(+), 51 deletions(-) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index a745a65103..9aecccee14 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -25,6 +25,27 @@ namespace osu.Game.Skinning { private readonly IStorageResourceProvider resources; + private static readonly IReadOnlyDictionary sample_mapping + = new Dictionary + { + { GameplaySkinSamples.ResultScoreTick, @"Results/score-tick" }, + { GameplaySkinSamples.ResultBadgeTick, @"Results/badge-dink" }, + { GameplaySkinSamples.ResultBadgeTickMax, @"Results/badge-dink-max" }, + { GameplaySkinSamples.ResultSwooshUp, @"Results/swoosh-up" }, + { GameplaySkinSamples.ResultRank_D, @"Results/rank-impact-fail-d" }, + { GameplaySkinSamples.ResultRank_B, @"Results/rank-impact-fail" }, + { GameplaySkinSamples.ResultRank_C, @"Results/rank-impact-fail" }, + { GameplaySkinSamples.ResultRank_A, @"Results/rank-impact-pass" }, + { GameplaySkinSamples.ResultRank_S, @"Results/rank-impact-pass" }, + { GameplaySkinSamples.ResultRank_SS, @"Results/rank-impact-pass-ss" }, + { GameplaySkinSamples.ResultApplause_D, @"Results/applause-d" }, + { GameplaySkinSamples.ResultApplause_B, @"Results/applause-b" }, + { GameplaySkinSamples.ResultApplause_C, @"Results/applause-c" }, + { GameplaySkinSamples.ResultApplause_A, @"Results/applause-a" }, + { GameplaySkinSamples.ResultApplause_S, @"Results/applause-s" }, + { GameplaySkinSamples.ResultApplause_SS, @"Results/applause-s" } + }; + public DefaultSkin(IStorageResourceProvider resources) : this(SkinInfo.Default, resources) { @@ -60,56 +81,8 @@ namespace osu.Game.Skinning switch (component) { case GameplaySkinComponent sample: - switch (sample.Component) - { - case GameplaySkinSamples.ResultScoreTick: - return new DrawableSample(GetSample(new SampleInfo("Results/score-tick"))); - - case GameplaySkinSamples.ResultBadgeTick: - return new DrawableSample(GetSample(new SampleInfo("Results/badge-dink"))); - - case GameplaySkinSamples.ResultBadgeTickMax: - return new DrawableSample(GetSample(new SampleInfo("Results/badge-dink-max"))); - - case GameplaySkinSamples.ResultSwooshUp: - return new DrawableSample(GetSample(new SampleInfo("Results/swoosh-up"))); - - case GameplaySkinSamples.ResultRank_D: - return new DrawableSample(GetSample(new SampleInfo("Results/rank-impact-fail-d"))); - - case GameplaySkinSamples.ResultRank_B: - return new DrawableSample(GetSample(new SampleInfo("Results/rank-impact-fail"))); - - case GameplaySkinSamples.ResultRank_C: - return new DrawableSample(GetSample(new SampleInfo("Results/rank-impact-fail"))); - - case GameplaySkinSamples.ResultRank_A: - return new DrawableSample(GetSample(new SampleInfo("Results/rank-impact-pass"))); - - case GameplaySkinSamples.ResultRank_S: - return new DrawableSample(GetSample(new SampleInfo("Results/rank-impact-pass"))); - - case GameplaySkinSamples.ResultRank_SS: - return new DrawableSample(GetSample(new SampleInfo("Results/rank-impact-pass-ss"))); - - case GameplaySkinSamples.ResultApplause_D: - return new DrawableSample(GetSample(new SampleInfo("Results/applause-d"))); - - case GameplaySkinSamples.ResultApplause_B: - return new DrawableSample(GetSample(new SampleInfo("Results/applause-b"))); - - case GameplaySkinSamples.ResultApplause_C: - return new DrawableSample(GetSample(new SampleInfo("Results/applause-c"))); - - case GameplaySkinSamples.ResultApplause_A: - return new DrawableSample(GetSample(new SampleInfo("Results/applause-a"))); - - case GameplaySkinSamples.ResultApplause_S: - return new DrawableSample(GetSample(new SampleInfo("Results/applause-s"))); - - case GameplaySkinSamples.ResultApplause_SS: - return new DrawableSample(GetSample(new SampleInfo("Results/applause-s"))); - } + if (sample_mapping.ContainsKey(sample.Component)) + return new DrawableSample(GetSample(new SampleInfo(sample_mapping[sample.Component]))); break; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a484516217..9fcfd646d5 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -425,7 +425,7 @@ namespace osu.Game.Skinning case GameplaySkinSamples.ResultApplause_SS: if (applause != null) // Legacy skins don't have sounds for the result screen, but may instead have an 'applause' sound. - // This lets a legacy skin's applause sound play instead of result screen sounds (as to not play over each other) + // This lets a legacy skin's applause sound play instead of result screen sounds (as to not play over each other) return Drawable.Empty(); break; From b746fe7c03c3ca863ddd9825111b3b9b95d9de14 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 5 Jun 2021 11:03:49 +0200 Subject: [PATCH 201/520] Fix binding order --- osu.Game/Online/Chat/MessageNotifier.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 25d6795ffa..c70e678843 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -36,19 +36,18 @@ namespace osu.Game.Online.Chat private Bindable notifyOnMention; private Bindable notifyOnPM; private readonly IBindable localUser = new Bindable(); - private readonly BindableList joinedChannels = new BindableList(); + private readonly IBindableList joinedChannels = new BindableList(); [BackgroundDependencyLoader] private void load(OsuConfigManager config, IAPIProvider api) { notifyOnMention = config.GetBindable(OsuSetting.ChatHighlightName); notifyOnPM = config.GetBindable(OsuSetting.ChatMessageNotification); - api.LocalUser.BindTo(localUser); + localUser.BindTo(api.LocalUser); // Listen for new messages joinedChannels.CollectionChanged += channelsChanged; - - channelManager.JoinedChannels.BindTo(joinedChannels); + joinedChannels.BindTo(channelManager.JoinedChannels); } private void channelsChanged(object sender, NotifyCollectionChangedEventArgs e) From 6e40af756b71bc9544b000ca310174be17bc8da6 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 5 Jun 2021 11:10:16 +0200 Subject: [PATCH 202/520] Add request handler for dummy API --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 26b0063178..a1c68d34d1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -28,6 +29,14 @@ namespace osu.Game.Tests.Visual.Online [SetUp] public void Setup() { + // We blindly mark every request as success so that ChannelManager doesn't remove our channel again. + if (API is DummyAPIAccess daa) + { + daa.HandleRequest = (request) => { + return true; + }; + } + friend = new User { Id = 0, Username = "Friend" }; publicChannel = new Channel { Id = 1, Name = "osu" }; privateMessageChannel = new Channel(friend) { Id = 2, Name = friend.Username, Type = ChannelType.PM }; From ce4bcda8032d19d9364cad23b5882536d411886b Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 5 Jun 2021 14:02:48 +0200 Subject: [PATCH 203/520] Use separate method for fetching channel objects Resolves a pull request review --- osu.Game/Online/Chat/MessageNotifier.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index c70e678843..c52a1876b1 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -76,9 +76,18 @@ namespace osu.Game.Online.Chat HandleMessages(messages.First().ChannelId, messages); } + /// + /// Searches for a channel with the matching , returns when none found. + /// + private Channel fetchJoinedChannel(long channelId) + { + return channelManager.JoinedChannels.SingleOrDefault(c => c.Id == channelId); + } + public void HandleMessages(long channelId, IEnumerable messages) { - var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == channelId); + // Fetch channel object + var channel = fetchJoinedChannel(channelId); if (channel == null) { @@ -86,11 +95,6 @@ namespace osu.Game.Online.Chat return; } - HandleMessages(channel, messages); - } - - public void HandleMessages(Channel channel, IEnumerable messages) - { // Only send notifications, if ChatOverlay and the target channel aren't visible. if (chatOverlay?.IsPresent == true && channelManager.CurrentChannel.Value == channel) return; From 5e44329e0b0e7f5221c90e69baed3f73664084cc Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 5 Jun 2021 14:42:16 +0200 Subject: [PATCH 204/520] Add DummyAPIAccess request handler Make CreateChannelRequest.channel public --- .../Visual/Online/TestSceneMessageNotifier.cs | 34 ++++++++++++++++--- .../API/Requests/CreateChannelRequest.cs | 6 ++-- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index a1c68d34d1..fada645632 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.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.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -9,6 +10,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -29,12 +32,9 @@ namespace osu.Game.Tests.Visual.Online [SetUp] public void Setup() { - // We blindly mark every request as success so that ChannelManager doesn't remove our channel again. if (API is DummyAPIAccess daa) { - daa.HandleRequest = (request) => { - return true; - }; + daa.HandleRequest = dummyAPIHandleRequest; } friend = new User { Id = 0, Username = "Friend" }; @@ -52,6 +52,32 @@ namespace osu.Game.Tests.Visual.Online }); } + private bool dummyAPIHandleRequest(APIRequest request) + { + switch (request) + { + case GetMessagesRequest messagesRequest: + messagesRequest.TriggerSuccess(new List(0)); + return true; + + case CreateChannelRequest createChannelRequest: + var apiChatChannel = new APIChatChannel + { + RecentMessages = new List(0), + ChannelID = (int)createChannelRequest.Channel.Id + }; + createChannelRequest.TriggerSuccess(apiChatChannel); + return true; + + case ListChannelsRequest listChannelsRequest: + listChannelsRequest.TriggerSuccess(new List(1) { publicChannel }); + return true; + + default: + return false; + } + } + [Test] public void TestPublicChannelMention() { diff --git a/osu.Game/Online/API/Requests/CreateChannelRequest.cs b/osu.Game/Online/API/Requests/CreateChannelRequest.cs index 42cb201969..041ad26267 100644 --- a/osu.Game/Online/API/Requests/CreateChannelRequest.cs +++ b/osu.Game/Online/API/Requests/CreateChannelRequest.cs @@ -11,11 +11,11 @@ namespace osu.Game.Online.API.Requests { public class CreateChannelRequest : APIRequest { - private readonly Channel channel; + public readonly Channel Channel; public CreateChannelRequest(Channel channel) { - this.channel = channel; + Channel = channel; } protected override WebRequest CreateWebRequest() @@ -24,7 +24,7 @@ namespace osu.Game.Online.API.Requests req.Method = HttpMethod.Post; req.AddParameter("type", $"{ChannelType.PM}"); - req.AddParameter("target_id", $"{channel.Users.First().Id}"); + req.AddParameter("target_id", $"{Channel.Users.First().Id}"); return req; } From 248e90df6d492baebd808188b1fa47f96527f7f7 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 5 Jun 2021 15:55:58 +0200 Subject: [PATCH 205/520] Add more request handling code --- .../Visual/Online/TestSceneMessageNotifier.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index fada645632..28ec3e91a1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -73,6 +73,18 @@ namespace osu.Game.Tests.Visual.Online listChannelsRequest.TriggerSuccess(new List(1) { publicChannel }); return true; + case GetUpdatesRequest updatesRequest: + updatesRequest.TriggerSuccess(new GetUpdatesResponse + { + Messages = new List(0), + Presence = new List(0) + }); + return true; + + case JoinChannelRequest joinChannelRequest: + joinChannelRequest.TriggerSuccess(); + return true; + default: return false; } From 4925a7d59e36f10e3b089287e0700a09362df8ce Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 5 Jun 2021 15:57:14 +0200 Subject: [PATCH 206/520] Minor code quality changes --- osu.Game/Online/Chat/MessageNotifier.cs | 27 ++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index c52a1876b1..53bd3c61c3 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -113,30 +113,47 @@ namespace osu.Game.Online.Chat if (checkForPMs(channel, message)) continue; - // change output to bool again if another "message processor" is added. - checkForMentions(channel, message, localUser.Value.Username); + _ = checkForMentions(channel, message, localUser.Value.Username); } } + /// + /// Checks whether the user enabled private message notifications and whether specified is a direct message. + /// + /// The channel associated to the + /// The message to be checked private bool checkForPMs(Channel channel, Message message) { if (!notifyOnPM.Value || channel.Type != ChannelType.PM) return false; - var notification = new PrivateMessageNotification(message.Sender.Username, channel); + if (channel.Id != message.ChannelId) + throw new ArgumentException("The provided channel doesn't match with the channel id provided by the message parameter.", nameof(channel)); + var notification = new PrivateMessageNotification(message.Sender.Username, channel); notificationOverlay?.Post(notification); return true; } - private void checkForMentions(Channel channel, Message message, string username) + /// + /// Checks whether the user enabled mention notifications and whether specified mentions the provided . + /// + /// The channel associated to the + /// The message to be checked + /// The username that will be checked for + private bool checkForMentions(Channel channel, Message message, string username) { if (!notifyOnMention.Value || !isMentioning(message.Content, username)) - return; + return false; + + if (channel.Id != message.ChannelId) + throw new ArgumentException("The provided channel doesn't match with the channel id provided by the message parameter.", nameof(channel)); var notification = new MentionNotification(message.Sender.Username, channel); notificationOverlay?.Post(notification); + + return true; } /// From b97f31f31414a8cc6c284ce2239d2cdecaf64500 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 5 Jun 2021 19:03:11 +0200 Subject: [PATCH 207/520] Revert deletion of xmldoc summary line --- osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 001520b507..c0de093425 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -60,6 +60,7 @@ namespace osu.Game.Overlays.Chat.Tabs /// /// Adds a channel to the ChannelTabControl. + /// The first channel added will automaticly selected. /// /// The channel that is going to be added. public void AddChannel(Channel channel) From c452715bf190e7aaa8aef11612cd0f56e35b5561 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Sun, 6 Jun 2021 01:18:30 -0400 Subject: [PATCH 208/520] Allow skin elements to find closest anchor - Resolves ppy/osu#13252 - Add localisation strings for the context menu instead of using enum --- osu.Game/Localisation/SkinEditorStrings.cs | 74 ++++++ .../Skinning/Editor/SkinSelectionHandler.cs | 215 +++++++++++++++--- 2 files changed, 262 insertions(+), 27 deletions(-) create mode 100644 osu.Game/Localisation/SkinEditorStrings.cs diff --git a/osu.Game/Localisation/SkinEditorStrings.cs b/osu.Game/Localisation/SkinEditorStrings.cs new file mode 100644 index 0000000000..24a077963f --- /dev/null +++ b/osu.Game/Localisation/SkinEditorStrings.cs @@ -0,0 +1,74 @@ +// 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; + +namespace osu.Game.Localisation +{ + public static class SkinEditorStrings + { + private const string prefix = "osu.Game.Localisation.SkinEditor"; + + /// + /// "anchor" + /// + public static LocalisableString Anchor => new TranslatableString(getKey("anchor"), "anchor"); + + /// + /// "origin" + /// + public static LocalisableString Origin => new TranslatableString(getKey("origin"), "origin"); + + /// + /// "top-left" + /// + public static LocalisableString TopLeft => new TranslatableString(getKey("top_left"), "top-left"); + + /// + /// "top-centre" + /// + public static LocalisableString TopCentre => new TranslatableString(getKey("top_centre"), "top-centre"); + + /// + /// "top-right" + /// + public static LocalisableString TopRight => new TranslatableString(getKey("top_right"), "top-right"); + + /// + /// "centre-left" + /// + public static LocalisableString CentreLeft => new TranslatableString(getKey("centre_left"), "centre-left"); + + /// + /// "centre" + /// + public static LocalisableString Centre => new TranslatableString(getKey("centre"), "centre"); + + /// + /// "centre-right" + /// + public static LocalisableString CentreRight => new TranslatableString(getKey("centre_right"), "centre-right"); + + /// + /// "bottom-left" + /// + public static LocalisableString BottomLeft => new TranslatableString(getKey("bottom_left"), "bottom-left"); + + /// + /// "bottom-centre" + /// + public static LocalisableString BottomCentre => new TranslatableString(getKey("bottom_centre"), "bottom-centre"); + + /// + /// "bottom-right" + /// + public static LocalisableString BottomRight => new TranslatableString(getKey("bottom_right"), "bottom-right"); + + /// + /// "closest" + /// + public static LocalisableString Closest => new TranslatableString(getKey("closest"), "closest"); + + private static string getKey(string key) => $"{prefix}:{key}"; + } +} diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 9cca0ba2c7..956f6c79f9 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -4,22 +4,152 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components; using osuTK; +using Humanizer; +using osu.Game.Localisation; namespace osu.Game.Skinning.Editor { public class SkinSelectionHandler : SelectionHandler { + /// + ///

Keeps track of whether a is using the closest anchor point within its parent, + /// or whether the user is overriding its anchor point.

+ ///

Each key is either a direct cast of an Anchor value, or it is equal to . This is done + /// because the "Closest" menu item is not a valid anchor, so something other than an anchor must be used.

+ ///

Each value is a . If the is , the user has + /// overridden the anchor point. + /// If , the closest anchor point is assigned to the Drawable when it is either dragged by the user via , or when "Closest" is assigned from + /// the anchor context menu via .

+ ///
+ /// + ///

A ConditionalWeakTable is preferable to a Dictionary because a Dictionary will keep + /// orphaned references to a Drawable forever, unless manually pruned

+ ///

is used as a thin wrapper around bool because ConditionalWeakTable requires a reference type as both a key and a value.

+ ///
+ private readonly ConditionalWeakTable isDrawableUsingClosestAnchorLookup = new ConditionalWeakTable(); + + /// + /// The hash code of the "Closest" menu item in the anchor point context menu. + /// + /// This does not need to change with locale; it need only be constant and distinct from any values. + private static readonly int hash_of_closest_anchor_menu_item = @"Closest".GetHashCode(); + + /// Used by to populate and . + private static readonly LocalisableString[] unbound_anchor_menu_items = + { + SkinEditorStrings.Closest, + SkinEditorStrings.TopLeft, + SkinEditorStrings.TopCentre, + SkinEditorStrings.TopRight, + SkinEditorStrings.CentreLeft, + SkinEditorStrings.Centre, + SkinEditorStrings.CentreRight, + SkinEditorStrings.BottomLeft, + SkinEditorStrings.BottomCentre, + SkinEditorStrings.BottomRight, + }; + + /// Used by to populate and . + private static readonly int[] anchor_menu_hashes = + new[] + { + Anchor.TopLeft, + Anchor.TopCentre, + Anchor.TopRight, + Anchor.CentreLeft, + Anchor.Centre, + Anchor.CentreRight, + Anchor.BottomLeft, + Anchor.BottomCentre, + Anchor.BottomRight, + } + .Cast() + .Prepend(hash_of_closest_anchor_menu_item) + .ToArray(); + + private Dictionary localisedAnchorMenuItems; + private Dictionary localisedOriginMenuItems; + private ILocalisedBindableString localisedAnchor; + private ILocalisedBindableString localisedOrigin; + + [BackgroundDependencyLoader] + private void load(LocalisationManager localisation) + { + localisedAnchor = localisation.GetLocalisedString(SkinEditorStrings.Anchor); + localisedOrigin = localisation.GetLocalisedString(SkinEditorStrings.Origin); + + var boundAnchorMenuItems = unbound_anchor_menu_items.Select(localisation.GetLocalisedString).ToArray(); + + var anchorPairs = anchor_menu_hashes.Zip(boundAnchorMenuItems, (k, v) => new KeyValuePair(k, v)).ToArray(); + localisedAnchorMenuItems = new Dictionary(anchorPairs); + var originPairs = anchorPairs.Where(pair => pair.Key != hash_of_closest_anchor_menu_item); + localisedOriginMenuItems = new Dictionary(originPairs); + } + + private Anchor getClosestAnchorForDrawable(Drawable drawable) + { + var parent = drawable.Parent; + if (parent == null) + return drawable.Anchor; + + // If there is a better way to get this information, let me know. Was taken from LogoTrackingContainer.ComputeLogoTrackingPosition + // I tried a lot of different things, such as just using Position / ChildSize, but none of them worked properly. + var screenPosition = getOriginPositionFromQuad(drawable.ScreenSpaceDrawQuad, drawable.Origin); + var absolutePosition = parent.ToLocalSpace(screenPosition); + var factor = parent.RelativeToAbsoluteFactor; + + var result = default(Anchor); + + static Anchor getTieredComponent(float component, Anchor tier0, Anchor tier1, Anchor tier2) => + component >= 2 / 3f + ? tier2 + : component >= 1 / 3f + ? tier1 + : tier0; + + result |= getTieredComponent(absolutePosition.X / factor.X, Anchor.x0, Anchor.x1, Anchor.x2); + result |= getTieredComponent(absolutePosition.Y / factor.Y, Anchor.y0, Anchor.y1, Anchor.y2); + + return result; + } + + private Vector2 getOriginPositionFromQuad(in Quad quad, Anchor origin) + { + var result = quad.TopLeft; + + if (origin.HasFlagFast(Anchor.x2)) + result.X += quad.Width; + else if (origin.HasFlagFast(Anchor.x1)) + result.X += quad.Width / 2f; + + if (origin.HasFlagFast(Anchor.y2)) + result.Y += quad.Height; + else if (origin.HasFlagFast(Anchor.y1)) + result.Y += quad.Height / 2f; + + return result; + } + + /// Defaults to , meaning anchors are closest by default. + private BindableBool isDrawableUsingClosestAnchor(Drawable drawable) => isDrawableUsingClosestAnchorLookup.GetValue(drawable, _ => new BindableBool(true)); + + // There may be a more generalised form of this somewhere in the codebase. If so, use that. + private static string getSentenceCaseLocalisedString(ILocalisedBindableString ls) => ls.Value.Transform(To.SentenceCase); + [Resolved] private SkinEditor skinEditor { get; set; } @@ -151,11 +281,24 @@ namespace osu.Game.Skinning.Editor { Drawable drawable = (Drawable)c.Item; drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); + + updateDrawableAnchorIfUsingClosest(drawable); } return true; } + private void updateDrawableAnchorIfUsingClosest(Drawable drawable) + { + if (!isDrawableUsingClosestAnchor(drawable).Value) return; + + var closestAnchor = getClosestAnchorForDrawable(drawable); + + if (closestAnchor == drawable.Anchor) return; + + updateDrawableAnchor(drawable, closestAnchor); + } + protected override void OnSelectionChanged() { base.OnSelectionChanged(); @@ -171,42 +314,35 @@ namespace osu.Game.Skinning.Editor protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { - yield return new OsuMenuItem("Anchor") + int checkAnchor(Drawable drawable) => + isDrawableUsingClosestAnchor(drawable).Value + ? hash_of_closest_anchor_menu_item + : (int)drawable.Anchor; + + yield return new OsuMenuItem(getSentenceCaseLocalisedString(localisedAnchor)) { - Items = createAnchorItems(d => d.Anchor, applyAnchor).ToArray() + Items = createAnchorItems(localisedAnchorMenuItems, checkAnchor, applyAnchor).ToArray() }; - yield return new OsuMenuItem("Origin") + yield return new OsuMenuItem(getSentenceCaseLocalisedString(localisedOrigin)) { - Items = createAnchorItems(d => d.Origin, applyOrigin).ToArray() + // Origins can't be "closest" so we just cast to int + Items = createAnchorItems(localisedOriginMenuItems, d => (int)d.Origin, applyOrigin).ToArray() }; foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; - IEnumerable createAnchorItems(Func checkFunction, Action applyFunction) - { - var displayableAnchors = new[] + IEnumerable createAnchorItems(IDictionary items, Func checkFunction, Action applyFunction) => + items.Select(pair => { - Anchor.TopLeft, - Anchor.TopCentre, - Anchor.TopRight, - Anchor.CentreLeft, - Anchor.Centre, - Anchor.CentreRight, - Anchor.BottomLeft, - Anchor.BottomCentre, - Anchor.BottomRight, - }; + var (hash, ls) = pair; - return displayableAnchors.Select(a => - { - return new TernaryStateRadioMenuItem(a.ToString(), MenuItemType.Standard, _ => applyFunction(a)) + return new TernaryStateRadioMenuItem(getSentenceCaseLocalisedString(ls), MenuItemType.Standard, _ => applyFunction(hash)) { - State = { Value = GetStateFromSelection(selection, c => checkFunction((Drawable)c.Item) == a) } + State = { Value = GetStateFromSelection(selection, c => checkFunction((Drawable)c.Item) == hash) } }; }); - } } private static void updateDrawablePosition(Drawable drawable, Vector2 screenSpacePosition) @@ -215,8 +351,10 @@ namespace osu.Game.Skinning.Editor drawable.Parent.ToLocalSpace(screenSpacePosition) - drawable.AnchorPosition; } - private void applyOrigin(Anchor anchor) + private void applyOrigin(int hash) { + var anchor = (Anchor)hash; + foreach (var item in SelectedItems) { var drawable = (Drawable)item; @@ -224,6 +362,8 @@ namespace osu.Game.Skinning.Editor var previousOrigin = drawable.OriginPosition; drawable.Origin = anchor; drawable.Position += drawable.OriginPosition - previousOrigin; + + updateDrawableAnchorIfUsingClosest(drawable); } } @@ -234,18 +374,39 @@ namespace osu.Game.Skinning.Editor private Quad getSelectionQuad() => GetSurroundingQuad(SelectedBlueprints.SelectMany(b => b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); - private void applyAnchor(Anchor anchor) + private void applyAnchor(int hash) { foreach (var item in SelectedItems) { var drawable = (Drawable)item; - var previousAnchor = drawable.AnchorPosition; - drawable.Anchor = anchor; - drawable.Position -= drawable.AnchorPosition - previousAnchor; + var anchor = getAnchorFromHashAndDrawableAndRecordWhetherUsingClosestAnchor(hash, drawable); + + updateDrawableAnchor(drawable, anchor); } } + private static void updateDrawableAnchor(Drawable drawable, Anchor anchor) + { + var previousAnchor = drawable.AnchorPosition; + drawable.Anchor = anchor; + drawable.Position -= drawable.AnchorPosition - previousAnchor; + } + + private Anchor getAnchorFromHashAndDrawableAndRecordWhetherUsingClosestAnchor(int hash, Drawable drawable) + { + var isUsingClosestAnchor = isDrawableUsingClosestAnchor(drawable); + + if (hash == hash_of_closest_anchor_menu_item) + { + isUsingClosestAnchor.Value = true; + return getClosestAnchorForDrawable(drawable); + } + + isUsingClosestAnchor.Value = false; + return (Anchor)hash; + } + private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) { // cancel out scale in axes we don't care about (based on which drag handle was used). From 4aee76456f8cdb8bade6d037d1a9bb02af125ee8 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Sun, 6 Jun 2021 05:34:32 -0400 Subject: [PATCH 209/520] Replace localised strings with static English --- osu.Game/Localisation/SkinEditorStrings.cs | 74 ------------- .../Skinning/Editor/SkinSelectionHandler.cs | 101 +++++++----------- 2 files changed, 36 insertions(+), 139 deletions(-) delete mode 100644 osu.Game/Localisation/SkinEditorStrings.cs diff --git a/osu.Game/Localisation/SkinEditorStrings.cs b/osu.Game/Localisation/SkinEditorStrings.cs deleted file mode 100644 index 24a077963f..0000000000 --- a/osu.Game/Localisation/SkinEditorStrings.cs +++ /dev/null @@ -1,74 +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 osu.Framework.Localisation; - -namespace osu.Game.Localisation -{ - public static class SkinEditorStrings - { - private const string prefix = "osu.Game.Localisation.SkinEditor"; - - /// - /// "anchor" - /// - public static LocalisableString Anchor => new TranslatableString(getKey("anchor"), "anchor"); - - /// - /// "origin" - /// - public static LocalisableString Origin => new TranslatableString(getKey("origin"), "origin"); - - /// - /// "top-left" - /// - public static LocalisableString TopLeft => new TranslatableString(getKey("top_left"), "top-left"); - - /// - /// "top-centre" - /// - public static LocalisableString TopCentre => new TranslatableString(getKey("top_centre"), "top-centre"); - - /// - /// "top-right" - /// - public static LocalisableString TopRight => new TranslatableString(getKey("top_right"), "top-right"); - - /// - /// "centre-left" - /// - public static LocalisableString CentreLeft => new TranslatableString(getKey("centre_left"), "centre-left"); - - /// - /// "centre" - /// - public static LocalisableString Centre => new TranslatableString(getKey("centre"), "centre"); - - /// - /// "centre-right" - /// - public static LocalisableString CentreRight => new TranslatableString(getKey("centre_right"), "centre-right"); - - /// - /// "bottom-left" - /// - public static LocalisableString BottomLeft => new TranslatableString(getKey("bottom_left"), "bottom-left"); - - /// - /// "bottom-centre" - /// - public static LocalisableString BottomCentre => new TranslatableString(getKey("bottom_centre"), "bottom-centre"); - - /// - /// "bottom-right" - /// - public static LocalisableString BottomRight => new TranslatableString(getKey("bottom_right"), "bottom-right"); - - /// - /// "closest" - /// - public static LocalisableString Closest => new TranslatableString(getKey("closest"), "closest"); - - private static string getKey(string key) => $"{prefix}:{key}"; - } -} diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 956f6c79f9..fbe7cc6d91 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -11,15 +11,12 @@ using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components; using osuTK; -using Humanizer; -using osu.Game.Localisation; namespace osu.Game.Skinning.Editor { @@ -28,7 +25,7 @@ namespace osu.Game.Skinning.Editor /// ///

Keeps track of whether a is using the closest anchor point within its parent, /// or whether the user is overriding its anchor point.

- ///

Each key is either a direct cast of an Anchor value, or it is equal to . This is done + ///

Each key is either a direct cast of an Anchor value, or it is equal to . This is done /// because the "Closest" menu item is not a valid anchor, so something other than an anchor must be used.

///

Each value is a . If the is , the user has /// overridden the anchor point. @@ -42,62 +39,39 @@ namespace osu.Game.Skinning.Editor /// private readonly ConditionalWeakTable isDrawableUsingClosestAnchorLookup = new ConditionalWeakTable(); + private const string closest_text = @"Closest"; + ///

/// The hash code of the "Closest" menu item in the anchor point context menu. /// - /// This does not need to change with locale; it need only be constant and distinct from any values. - private static readonly int hash_of_closest_anchor_menu_item = @"Closest".GetHashCode(); + /// Needs only be constant and distinct from any values. + private static readonly int closest_text_hash = closest_text.GetHashCode(); - /// Used by to populate and . - private static readonly LocalisableString[] unbound_anchor_menu_items = + private static readonly Dictionary anchor_menu_items; + private static readonly Dictionary origin_menu_items; + + static SkinSelectionHandler() { - SkinEditorStrings.Closest, - SkinEditorStrings.TopLeft, - SkinEditorStrings.TopCentre, - SkinEditorStrings.TopRight, - SkinEditorStrings.CentreLeft, - SkinEditorStrings.Centre, - SkinEditorStrings.CentreRight, - SkinEditorStrings.BottomLeft, - SkinEditorStrings.BottomCentre, - SkinEditorStrings.BottomRight, - }; + var anchorMenuSubset = new[] + { + Anchor.TopLeft, + Anchor.TopCentre, + Anchor.TopRight, + Anchor.CentreLeft, + Anchor.Centre, + Anchor.CentreRight, + Anchor.BottomLeft, + Anchor.BottomCentre, + Anchor.BottomRight, + }; - /// Used by to populate and . - private static readonly int[] anchor_menu_hashes = - new[] - { - Anchor.TopLeft, - Anchor.TopCentre, - Anchor.TopRight, - Anchor.CentreLeft, - Anchor.Centre, - Anchor.CentreRight, - Anchor.BottomLeft, - Anchor.BottomCentre, - Anchor.BottomRight, - } - .Cast() - .Prepend(hash_of_closest_anchor_menu_item) - .ToArray(); + var texts = anchorMenuSubset.Select(a => a.ToString()).Prepend(closest_text).ToArray(); + var hashes = anchorMenuSubset.Cast().Prepend(closest_text_hash).ToArray(); - private Dictionary localisedAnchorMenuItems; - private Dictionary localisedOriginMenuItems; - private ILocalisedBindableString localisedAnchor; - private ILocalisedBindableString localisedOrigin; - - [BackgroundDependencyLoader] - private void load(LocalisationManager localisation) - { - localisedAnchor = localisation.GetLocalisedString(SkinEditorStrings.Anchor); - localisedOrigin = localisation.GetLocalisedString(SkinEditorStrings.Origin); - - var boundAnchorMenuItems = unbound_anchor_menu_items.Select(localisation.GetLocalisedString).ToArray(); - - var anchorPairs = anchor_menu_hashes.Zip(boundAnchorMenuItems, (k, v) => new KeyValuePair(k, v)).ToArray(); - localisedAnchorMenuItems = new Dictionary(anchorPairs); - var originPairs = anchorPairs.Where(pair => pair.Key != hash_of_closest_anchor_menu_item); - localisedOriginMenuItems = new Dictionary(originPairs); + var anchorPairs = hashes.Zip(texts, (k, v) => new KeyValuePair(k, v)).ToArray(); + anchor_menu_items = new Dictionary(anchorPairs); + var originPairs = anchorPairs.Where(pair => pair.Key != closest_text_hash); + origin_menu_items = new Dictionary(originPairs); } private Anchor getClosestAnchorForDrawable(Drawable drawable) @@ -147,9 +121,6 @@ namespace osu.Game.Skinning.Editor /// Defaults to , meaning anchors are closest by default. private BindableBool isDrawableUsingClosestAnchor(Drawable drawable) => isDrawableUsingClosestAnchorLookup.GetValue(drawable, _ => new BindableBool(true)); - // There may be a more generalised form of this somewhere in the codebase. If so, use that. - private static string getSentenceCaseLocalisedString(ILocalisedBindableString ls) => ls.Value.Transform(To.SentenceCase); - [Resolved] private SkinEditor skinEditor { get; set; } @@ -316,29 +287,29 @@ namespace osu.Game.Skinning.Editor { int checkAnchor(Drawable drawable) => isDrawableUsingClosestAnchor(drawable).Value - ? hash_of_closest_anchor_menu_item + ? closest_text_hash : (int)drawable.Anchor; - yield return new OsuMenuItem(getSentenceCaseLocalisedString(localisedAnchor)) + yield return new OsuMenuItem(nameof(Anchor)) { - Items = createAnchorItems(localisedAnchorMenuItems, checkAnchor, applyAnchor).ToArray() + Items = createAnchorItems(anchor_menu_items, checkAnchor, applyAnchor).ToArray() }; - yield return new OsuMenuItem(getSentenceCaseLocalisedString(localisedOrigin)) + yield return new OsuMenuItem(nameof(Origin)) { // Origins can't be "closest" so we just cast to int - Items = createAnchorItems(localisedOriginMenuItems, d => (int)d.Origin, applyOrigin).ToArray() + Items = createAnchorItems(origin_menu_items, d => (int)d.Origin, applyOrigin).ToArray() }; foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; - IEnumerable createAnchorItems(IDictionary items, Func checkFunction, Action applyFunction) => + IEnumerable createAnchorItems(IDictionary items, Func checkFunction, Action applyFunction) => items.Select(pair => { - var (hash, ls) = pair; + var (hash, text) = pair; - return new TernaryStateRadioMenuItem(getSentenceCaseLocalisedString(ls), MenuItemType.Standard, _ => applyFunction(hash)) + return new TernaryStateRadioMenuItem(text, MenuItemType.Standard, _ => applyFunction(hash)) { State = { Value = GetStateFromSelection(selection, c => checkFunction((Drawable)c.Item) == hash) } }; @@ -397,7 +368,7 @@ namespace osu.Game.Skinning.Editor { var isUsingClosestAnchor = isDrawableUsingClosestAnchor(drawable); - if (hash == hash_of_closest_anchor_menu_item) + if (hash == closest_text_hash) { isUsingClosestAnchor.Value = true; return getClosestAnchorForDrawable(drawable); From c9f5808bf2cba4966f9fb5a5e1347a9a31e13bf5 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Sun, 6 Jun 2021 06:58:21 -0400 Subject: [PATCH 210/520] Move lookup logic to DrawableExtensions This is now a global lookup to be shared by serialization and editor. --- osu.Game/Extensions/DrawableExtensions.cs | 25 +++++++++++++++++ .../Skinning/Editor/SkinSelectionHandler.cs | 28 ++----------------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 2ac6e6ff22..febad0d739 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -2,11 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Threading; using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Extensions @@ -63,5 +67,26 @@ namespace osu.Game.Extensions container.Add(child.CreateInstance()); } } + + /// + ///

A ConditionalWeakTable is preferable to a Dictionary because a Dictionary will keep + /// orphaned references to an forever, unless manually pruned.

+ ///

is used as a thin wrapper around bool because ConditionalWeakTable requires a reference type as both a key and a value.

+ ///

was chosen over because it is a common ancestor between (which is required for logic) + /// and (which is required for serialization via ).

+ ///

This collection is thread-safe according to the + /// documentation, + /// but the BindableBools are not unless leased.

+ ///
+ private static readonly ConditionalWeakTable is_drawable_using_closest_anchor_lookup = new ConditionalWeakTable(); + + /// + /// Gets or creates a representing whether is using the closest anchor point within its + /// parent. + /// + /// A whose is if the is using the closest anchor point, + /// otherwise . + public static BindableBool IsUsingClosestAnchor(this IDrawable drawable) => + is_drawable_using_closest_anchor_lookup.GetValue(drawable, _ => new BindableBool(true)); } } diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index fbe7cc6d91..5619a7ba19 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -4,9 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; @@ -22,23 +20,6 @@ namespace osu.Game.Skinning.Editor { public class SkinSelectionHandler : SelectionHandler { - /// - ///

Keeps track of whether a is using the closest anchor point within its parent, - /// or whether the user is overriding its anchor point.

- ///

Each key is either a direct cast of an Anchor value, or it is equal to . This is done - /// because the "Closest" menu item is not a valid anchor, so something other than an anchor must be used.

- ///

Each value is a . If the is , the user has - /// overridden the anchor point. - /// If , the closest anchor point is assigned to the Drawable when it is either dragged by the user via , or when "Closest" is assigned from - /// the anchor context menu via .

- ///
- /// - ///

A ConditionalWeakTable is preferable to a Dictionary because a Dictionary will keep - /// orphaned references to a Drawable forever, unless manually pruned

- ///

is used as a thin wrapper around bool because ConditionalWeakTable requires a reference type as both a key and a value.

- ///
- private readonly ConditionalWeakTable isDrawableUsingClosestAnchorLookup = new ConditionalWeakTable(); - private const string closest_text = @"Closest"; /// @@ -118,9 +99,6 @@ namespace osu.Game.Skinning.Editor return result; } - /// Defaults to , meaning anchors are closest by default. - private BindableBool isDrawableUsingClosestAnchor(Drawable drawable) => isDrawableUsingClosestAnchorLookup.GetValue(drawable, _ => new BindableBool(true)); - [Resolved] private SkinEditor skinEditor { get; set; } @@ -261,7 +239,7 @@ namespace osu.Game.Skinning.Editor private void updateDrawableAnchorIfUsingClosest(Drawable drawable) { - if (!isDrawableUsingClosestAnchor(drawable).Value) return; + if (!drawable.IsUsingClosestAnchor().Value) return; var closestAnchor = getClosestAnchorForDrawable(drawable); @@ -286,7 +264,7 @@ namespace osu.Game.Skinning.Editor protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { int checkAnchor(Drawable drawable) => - isDrawableUsingClosestAnchor(drawable).Value + drawable.IsUsingClosestAnchor().Value ? closest_text_hash : (int)drawable.Anchor; @@ -366,7 +344,7 @@ namespace osu.Game.Skinning.Editor private Anchor getAnchorFromHashAndDrawableAndRecordWhetherUsingClosestAnchor(int hash, Drawable drawable) { - var isUsingClosestAnchor = isDrawableUsingClosestAnchor(drawable); + var isUsingClosestAnchor = drawable.IsUsingClosestAnchor(); if (hash == closest_text_hash) { From 11b1b8c633a3f1f04decb76c5ea49050ad8931ee Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Sun, 6 Jun 2021 07:18:08 -0400 Subject: [PATCH 211/520] Add serialization support via SkinnableInfo --- osu.Game/Extensions/DrawableExtensions.cs | 1 + osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index febad0d739..d8c627ea8e 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -60,6 +60,7 @@ namespace osu.Game.Extensions component.Scale = info.Scale; component.Anchor = info.Anchor; component.Origin = info.Origin; + component.IsUsingClosestAnchor().Value = !info.IsNotUsingClosestAnchor; if (component is Container container) { diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index e08044b14c..3e829f6d38 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -32,6 +32,13 @@ namespace osu.Game.Screens.Play.HUD public Anchor Origin { get; set; } + /// + /// if this 's is + /// automatically determined by proximity, if the user has overridden it. + /// + /// Stored this way because default(bool) is and we want the default behaviour to be "closest". + public bool IsNotUsingClosestAnchor { get; set; } + public List Children { get; } = new List(); [JsonConstructor] @@ -52,6 +59,7 @@ namespace osu.Game.Screens.Play.HUD Scale = component.Scale; Anchor = component.Anchor; Origin = component.Origin; + IsNotUsingClosestAnchor = !component.IsUsingClosestAnchor().Value; if (component is Container container) { From 63346f6b756521bdac240777bcb26716cb2106ae Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Sun, 6 Jun 2021 09:40:58 -0400 Subject: [PATCH 212/520] Refactor getTieredComponent --- .../Skinning/Editor/SkinSelectionHandler.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 5619a7ba19..b1d353b0af 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -69,19 +69,23 @@ namespace osu.Game.Skinning.Editor var result = default(Anchor); - static Anchor getTieredComponent(float component, Anchor tier0, Anchor tier1, Anchor tier2) => - component >= 2 / 3f - ? tier2 - : component >= 1 / 3f - ? tier1 - : tier0; - result |= getTieredComponent(absolutePosition.X / factor.X, Anchor.x0, Anchor.x1, Anchor.x2); result |= getTieredComponent(absolutePosition.Y / factor.Y, Anchor.y0, Anchor.y1, Anchor.y2); return result; } + private static Anchor getTieredComponent(float component, Anchor tier0, Anchor tier1, Anchor tier2) + { + if (component >= 2 / 3f) + return tier2; + + if (component >= 1 / 3f) + return tier1; + + return tier0; + } + private Vector2 getOriginPositionFromQuad(in Quad quad, Anchor origin) { var result = quad.TopLeft; From da1c38d5a907ae2e4122b4d133f38a60949d0435 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Sun, 6 Jun 2021 10:10:55 -0400 Subject: [PATCH 213/520] Uninvert logic of SkinnableInfo.UsingClosestAnchor Also rename "IsUsingClosestAnchor" to simply "UsingClosestAnchor". --- osu.Game/Extensions/DrawableExtensions.cs | 4 ++-- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 10 +++++----- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index d8c627ea8e..f66fe2b8b2 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -60,7 +60,7 @@ namespace osu.Game.Extensions component.Scale = info.Scale; component.Anchor = info.Anchor; component.Origin = info.Origin; - component.IsUsingClosestAnchor().Value = !info.IsNotUsingClosestAnchor; + component.UsingClosestAnchor().Value = info.UsingClosestAnchor; if (component is Container container) { @@ -87,7 +87,7 @@ namespace osu.Game.Extensions /// /// A whose is if the is using the closest anchor point, /// otherwise . - public static BindableBool IsUsingClosestAnchor(this IDrawable drawable) => + public static BindableBool UsingClosestAnchor(this IDrawable drawable) => is_drawable_using_closest_anchor_lookup.GetValue(drawable, _ => new BindableBool(true)); } } diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 3e829f6d38..8ca5f1ba2d 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -33,11 +33,11 @@ namespace osu.Game.Screens.Play.HUD public Anchor Origin { get; set; } /// - /// if this 's is - /// automatically determined by proximity, if the user has overridden it. + ///

if this 's is + /// automatically determined by proximity, if the user has overridden it.

+ ///

Corresponds to at runtime.

///
- /// Stored this way because default(bool) is and we want the default behaviour to be "closest". - public bool IsNotUsingClosestAnchor { get; set; } + public bool UsingClosestAnchor { get; set; } = true; public List Children { get; } = new List(); @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Play.HUD Scale = component.Scale; Anchor = component.Anchor; Origin = component.Origin; - IsNotUsingClosestAnchor = !component.IsUsingClosestAnchor().Value; + UsingClosestAnchor = component.UsingClosestAnchor().Value; if (component is Container container) { diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index b1d353b0af..f8d41d94fa 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -243,7 +243,7 @@ namespace osu.Game.Skinning.Editor private void updateDrawableAnchorIfUsingClosest(Drawable drawable) { - if (!drawable.IsUsingClosestAnchor().Value) return; + if (!drawable.UsingClosestAnchor().Value) return; var closestAnchor = getClosestAnchorForDrawable(drawable); @@ -268,7 +268,7 @@ namespace osu.Game.Skinning.Editor protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { int checkAnchor(Drawable drawable) => - drawable.IsUsingClosestAnchor().Value + drawable.UsingClosestAnchor().Value ? closest_text_hash : (int)drawable.Anchor; @@ -348,7 +348,7 @@ namespace osu.Game.Skinning.Editor private Anchor getAnchorFromHashAndDrawableAndRecordWhetherUsingClosestAnchor(int hash, Drawable drawable) { - var isUsingClosestAnchor = drawable.IsUsingClosestAnchor(); + var isUsingClosestAnchor = drawable.UsingClosestAnchor(); if (hash == closest_text_hash) { From 888882ac63234f5372675218ea8449385f06bc33 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Sun, 6 Jun 2021 13:27:13 -0400 Subject: [PATCH 214/520] Remove first-person comment --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index f8d41d94fa..e07d4e351b 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -61,8 +61,6 @@ namespace osu.Game.Skinning.Editor if (parent == null) return drawable.Anchor; - // If there is a better way to get this information, let me know. Was taken from LogoTrackingContainer.ComputeLogoTrackingPosition - // I tried a lot of different things, such as just using Position / ChildSize, but none of them worked properly. var screenPosition = getOriginPositionFromQuad(drawable.ScreenSpaceDrawQuad, drawable.Origin); var absolutePosition = parent.ToLocalSpace(screenPosition); var factor = parent.RelativeToAbsoluteFactor; From 6a456e53f40a6a5f1b00d41957b0ba7b56907828 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Sun, 6 Jun 2021 13:28:17 -0400 Subject: [PATCH 215/520] Rename overly long method --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index e07d4e351b..5acc949182 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -331,7 +331,7 @@ namespace osu.Game.Skinning.Editor { var drawable = (Drawable)item; - var anchor = getAnchorFromHashAndDrawableAndRecordWhetherUsingClosestAnchor(hash, drawable); + var anchor = mapHashToAnchorSettings(hash, drawable); updateDrawableAnchor(drawable, anchor); } @@ -344,7 +344,7 @@ namespace osu.Game.Skinning.Editor drawable.Position -= drawable.AnchorPosition - previousAnchor; } - private Anchor getAnchorFromHashAndDrawableAndRecordWhetherUsingClosestAnchor(int hash, Drawable drawable) + private Anchor mapHashToAnchorSettings(int hash, Drawable drawable) { var isUsingClosestAnchor = drawable.UsingClosestAnchor(); From ce635af83edf43645cba2c6c6fcbe723faebe98a Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Sun, 6 Jun 2021 23:47:47 -0400 Subject: [PATCH 216/520] Add UsingClosestAnchor to ISkinnableDrawable Also implement it as an auto property in its inheritors. The auto properties default to true. --- osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs | 2 ++ osu.Game/Screens/Play/HUD/DefaultComboCounter.cs | 2 ++ osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs | 2 ++ osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs | 2 ++ osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs | 2 ++ osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 2 ++ osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 6 +----- osu.Game/Screens/Play/SongProgress.cs | 2 ++ osu.Game/Skinning/ISkinnableDrawable.cs | 6 ++++++ osu.Game/Skinning/LegacyAccuracyCounter.cs | 2 ++ osu.Game/Skinning/LegacyHealthDisplay.cs | 2 ++ osu.Game/Skinning/LegacyScoreCounter.cs | 2 ++ osu.Game/Skinning/SkinnableTargetComponentsContainer.cs | 2 ++ 13 files changed, 29 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs index 45ba05e036..5c6bb7299a 100644 --- a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs @@ -12,6 +12,8 @@ namespace osu.Game.Screens.Play.HUD [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } + public bool UsingClosestAnchor { get; set; } = true; + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index c4575c5ad0..97f33c0e07 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -17,6 +17,8 @@ namespace osu.Game.Screens.Play.HUD [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } + public bool UsingClosestAnchor { get; set; } = true; + public DefaultComboCounter() { Current.Value = DisplayedCount = 0; diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index ed297f0ffc..82f014d1e3 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -72,6 +72,8 @@ namespace osu.Game.Screens.Play.HUD } } + public bool UsingClosestAnchor { get; set; } = true; + public DefaultHealthDisplay() { Size = new Vector2(1, 5); diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs index 16e3642181..56eaab4408 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Play.HUD [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } + public bool UsingClosestAnchor { get; set; } = true; + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index b0f9928b13..f263b5accc 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -22,6 +22,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [Resolved] private OsuColour colours { get; set; } + public bool UsingClosestAnchor { get; set; } = true; + [BackgroundDependencyLoader(true)] private void load(DrawableRuleset drawableRuleset) { diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 1737634e31..9640bf1d4d 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -59,6 +59,8 @@ namespace osu.Game.Screens.Play.HUD set => counterContainer.Alpha = value ? 1 : 0; } + public bool UsingClosestAnchor { get; set; } = true; + public LegacyComboCounter() { AutoSizeAxes = Axes.Both; diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 8ca5f1ba2d..5c733a9bc1 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -32,11 +32,7 @@ namespace osu.Game.Screens.Play.HUD public Anchor Origin { get; set; } - /// - ///

if this 's is - /// automatically determined by proximity, if the user has overridden it.

- ///

Corresponds to at runtime.

- ///
+ /// public bool UsingClosestAnchor { get; set; } = true; public List Children { get; } = new List(); diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index cab44c7473..e8687b9ab1 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -78,6 +78,8 @@ namespace osu.Game.Screens.Play private IClock referenceClock; + public bool UsingClosestAnchor { get; set; } = true; + public SongProgress() { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Skinning/ISkinnableDrawable.cs b/osu.Game/Skinning/ISkinnableDrawable.cs index d42b6f71b0..a0c7ae43a2 100644 --- a/osu.Game/Skinning/ISkinnableDrawable.cs +++ b/osu.Game/Skinning/ISkinnableDrawable.cs @@ -14,5 +14,11 @@ namespace osu.Game.Skinning /// Whether this component should be editable by an end user. ///
bool IsEditable => true; + + /// + /// if this 's is automatically determined by proximity, + /// if the user has overridden it. + /// + bool UsingClosestAnchor { get; set; } } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 16562d9571..6c75ade365 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -12,6 +12,8 @@ namespace osu.Game.Skinning { public class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable { + public bool UsingClosestAnchor { get; set; } = true; + public LegacyAccuracyCounter() { Anchor = Anchor.TopRight; diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index c601adc3a0..d70d672189 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -30,6 +30,8 @@ namespace osu.Game.Skinning private bool isNewStyle; + public bool UsingClosestAnchor { get; set; } = true; + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 64ea03d59c..944e42ed0e 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -13,6 +13,8 @@ namespace osu.Game.Skinning protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; + public bool UsingClosestAnchor { get; set; } = true; + public LegacyScoreCounter() : base(6) { diff --git a/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs b/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs index 2107ca7a8b..6acd917991 100644 --- a/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs @@ -17,6 +17,8 @@ namespace osu.Game.Skinning { public bool IsEditable => false; + public bool UsingClosestAnchor { get; set; } = true; + private readonly Action applyDefaults; /// From f28916e30fafab0ac8148cc795bce2d0ad891cf8 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Mon, 7 Jun 2021 00:04:53 -0400 Subject: [PATCH 217/520] Remove all UsingClosestAnchor() extension logic It is replaced with ISkinnableDrawable.UsingClosestAnchor. --- osu.Game/Extensions/DrawableExtensions.cs | 28 ++----------------- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 5 +++- .../Skinning/Editor/SkinSelectionHandler.cs | 26 ++++++----------- 3 files changed, 15 insertions(+), 44 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index f66fe2b8b2..1199db587a 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -2,9 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; @@ -60,7 +57,9 @@ namespace osu.Game.Extensions component.Scale = info.Scale; component.Anchor = info.Anchor; component.Origin = info.Origin; - component.UsingClosestAnchor().Value = info.UsingClosestAnchor; + + if (component is ISkinnableDrawable skinnable) + skinnable.UsingClosestAnchor = info.UsingClosestAnchor; if (component is Container container) { @@ -68,26 +67,5 @@ namespace osu.Game.Extensions container.Add(child.CreateInstance()); } } - - /// - ///

A ConditionalWeakTable is preferable to a Dictionary because a Dictionary will keep - /// orphaned references to an forever, unless manually pruned.

- ///

is used as a thin wrapper around bool because ConditionalWeakTable requires a reference type as both a key and a value.

- ///

was chosen over because it is a common ancestor between (which is required for logic) - /// and (which is required for serialization via ).

- ///

This collection is thread-safe according to the - /// documentation, - /// but the BindableBools are not unless leased.

- ///
- private static readonly ConditionalWeakTable is_drawable_using_closest_anchor_lookup = new ConditionalWeakTable(); - - /// - /// Gets or creates a representing whether is using the closest anchor point within its - /// parent. - /// - /// A whose is if the is using the closest anchor point, - /// otherwise . - public static BindableBool UsingClosestAnchor(this IDrawable drawable) => - is_drawable_using_closest_anchor_lookup.GetValue(drawable, _ => new BindableBool(true)); } } diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 5c733a9bc1..a76634a1f0 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -55,7 +55,10 @@ namespace osu.Game.Screens.Play.HUD Scale = component.Scale; Anchor = component.Anchor; Origin = component.Origin; - UsingClosestAnchor = component.UsingClosestAnchor().Value; + + UsingClosestAnchor = + // true if it's not an ISkinnableDrawable + !(component is ISkinnableDrawable skinnable) || skinnable.UsingClosestAnchor; if (component is Container container) { diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 5acc949182..f4476b9de7 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -241,7 +241,7 @@ namespace osu.Game.Skinning.Editor private void updateDrawableAnchorIfUsingClosest(Drawable drawable) { - if (!drawable.UsingClosestAnchor().Value) return; + if (!(drawable is ISkinnableDrawable { UsingClosestAnchor: true })) return; var closestAnchor = getClosestAnchorForDrawable(drawable); @@ -265,8 +265,8 @@ namespace osu.Game.Skinning.Editor protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { - int checkAnchor(Drawable drawable) => - drawable.UsingClosestAnchor().Value + static int checkAnchor(Drawable drawable) => + drawable is ISkinnableDrawable { UsingClosestAnchor: true } ? closest_text_hash : (int)drawable.Anchor; @@ -331,8 +331,12 @@ namespace osu.Game.Skinning.Editor { var drawable = (Drawable)item; - var anchor = mapHashToAnchorSettings(hash, drawable); + var (usingClosest, anchor) = + hash == closest_text_hash + ? (true, getClosestAnchorForDrawable(drawable)) + : (false, (Anchor)hash); + item.UsingClosestAnchor = usingClosest; updateDrawableAnchor(drawable, anchor); } } @@ -344,20 +348,6 @@ namespace osu.Game.Skinning.Editor drawable.Position -= drawable.AnchorPosition - previousAnchor; } - private Anchor mapHashToAnchorSettings(int hash, Drawable drawable) - { - var isUsingClosestAnchor = drawable.UsingClosestAnchor(); - - if (hash == closest_text_hash) - { - isUsingClosestAnchor.Value = true; - return getClosestAnchorForDrawable(drawable); - } - - isUsingClosestAnchor.Value = false; - return (Anchor)hash; - } - private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) { // cancel out scale in axes we don't care about (based on which drag handle was used). From 133d72a8c0b02bc921160c1d70b2e960b11cb335 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Mon, 7 Jun 2021 00:14:36 -0400 Subject: [PATCH 218/520] Rename UsingClosestAnchor It is now "OverridesClosestAnchor". The logic is inverted accordingly. --- osu.Game/Extensions/DrawableExtensions.cs | 2 +- .../Screens/Play/HUD/DefaultAccuracyCounter.cs | 2 +- osu.Game/Screens/Play/HUD/DefaultComboCounter.cs | 2 +- .../Screens/Play/HUD/DefaultHealthDisplay.cs | 2 +- osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs | 2 +- .../Play/HUD/HitErrorMeters/HitErrorMeter.cs | 2 +- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 2 +- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 8 +++----- osu.Game/Screens/Play/SongProgress.cs | 2 +- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 16 ++++++++-------- osu.Game/Skinning/ISkinnableDrawable.cs | 6 +++--- osu.Game/Skinning/LegacyAccuracyCounter.cs | 2 +- osu.Game/Skinning/LegacyHealthDisplay.cs | 2 +- osu.Game/Skinning/LegacyScoreCounter.cs | 2 +- .../SkinnableTargetComponentsContainer.cs | 2 +- 15 files changed, 26 insertions(+), 28 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 1199db587a..11a8112ecf 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -59,7 +59,7 @@ namespace osu.Game.Extensions component.Origin = info.Origin; if (component is ISkinnableDrawable skinnable) - skinnable.UsingClosestAnchor = info.UsingClosestAnchor; + skinnable.OverridesClosestAnchor = info.OverridesClosestAnchor; if (component is Container container) { diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs index 5c6bb7299a..31edab5bc2 100644 --- a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Play.HUD [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - public bool UsingClosestAnchor { get; set; } = true; + public bool OverridesClosestAnchor { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index 97f33c0e07..bf9bed3fe3 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - public bool UsingClosestAnchor { get; set; } = true; + public bool OverridesClosestAnchor { get; set; } public DefaultComboCounter() { diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index 82f014d1e3..3488de70a9 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Play.HUD } } - public bool UsingClosestAnchor { get; set; } = true; + public bool OverridesClosestAnchor { get; set; } public DefaultHealthDisplay() { diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs index 56eaab4408..de534f516a 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Play.HUD [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - public bool UsingClosestAnchor { get; set; } = true; + public bool OverridesClosestAnchor { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index f263b5accc..a16adfebbc 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [Resolved] private OsuColour colours { get; set; } - public bool UsingClosestAnchor { get; set; } = true; + public bool OverridesClosestAnchor { get; set; } [BackgroundDependencyLoader(true)] private void load(DrawableRuleset drawableRuleset) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 9640bf1d4d..aff98b843b 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Play.HUD set => counterContainer.Alpha = value ? 1 : 0; } - public bool UsingClosestAnchor { get; set; } = true; + public bool OverridesClosestAnchor { get; set; } public LegacyComboCounter() { diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index a76634a1f0..207bea77ce 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -32,8 +32,8 @@ namespace osu.Game.Screens.Play.HUD public Anchor Origin { get; set; } - /// - public bool UsingClosestAnchor { get; set; } = true; + /// + public bool OverridesClosestAnchor { get; set; } public List Children { get; } = new List(); @@ -56,9 +56,7 @@ namespace osu.Game.Screens.Play.HUD Anchor = component.Anchor; Origin = component.Origin; - UsingClosestAnchor = - // true if it's not an ISkinnableDrawable - !(component is ISkinnableDrawable skinnable) || skinnable.UsingClosestAnchor; + OverridesClosestAnchor = component is ISkinnableDrawable { OverridesClosestAnchor: true }; if (component is Container container) { diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index e8687b9ab1..a00a2c0275 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play private IClock referenceClock; - public bool UsingClosestAnchor { get; set; } = true; + public bool OverridesClosestAnchor { get; set; } public SongProgress() { diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index f4476b9de7..7ddcebd662 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -241,7 +241,7 @@ namespace osu.Game.Skinning.Editor private void updateDrawableAnchorIfUsingClosest(Drawable drawable) { - if (!(drawable is ISkinnableDrawable { UsingClosestAnchor: true })) return; + if (drawable is ISkinnableDrawable { OverridesClosestAnchor: true }) return; var closestAnchor = getClosestAnchorForDrawable(drawable); @@ -266,9 +266,9 @@ namespace osu.Game.Skinning.Editor protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { static int checkAnchor(Drawable drawable) => - drawable is ISkinnableDrawable { UsingClosestAnchor: true } - ? closest_text_hash - : (int)drawable.Anchor; + drawable is ISkinnableDrawable { OverridesClosestAnchor: true } + ? (int)drawable.Anchor + : closest_text_hash; yield return new OsuMenuItem(nameof(Anchor)) { @@ -331,12 +331,12 @@ namespace osu.Game.Skinning.Editor { var drawable = (Drawable)item; - var (usingClosest, anchor) = + var (overridesClosest, anchor) = hash == closest_text_hash - ? (true, getClosestAnchorForDrawable(drawable)) - : (false, (Anchor)hash); + ? (false, getClosestAnchorForDrawable(drawable)) + : (true, (Anchor)hash); - item.UsingClosestAnchor = usingClosest; + item.OverridesClosestAnchor = overridesClosest; updateDrawableAnchor(drawable, anchor); } } diff --git a/osu.Game/Skinning/ISkinnableDrawable.cs b/osu.Game/Skinning/ISkinnableDrawable.cs index a0c7ae43a2..1b5c0df1b2 100644 --- a/osu.Game/Skinning/ISkinnableDrawable.cs +++ b/osu.Game/Skinning/ISkinnableDrawable.cs @@ -16,9 +16,9 @@ namespace osu.Game.Skinning bool IsEditable => true; /// - /// if this 's is automatically determined by proximity, - /// if the user has overridden it. + /// if this 's is automatically determined by proximity, + /// if the user has overridden it. /// - bool UsingClosestAnchor { get; set; } + bool OverridesClosestAnchor { get; set; } } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 6c75ade365..603f0ecffb 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -12,7 +12,7 @@ namespace osu.Game.Skinning { public class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable { - public bool UsingClosestAnchor { get; set; } = true; + public bool OverridesClosestAnchor { get; set; } public LegacyAccuracyCounter() { diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index d70d672189..a419f981b5 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Skinning private bool isNewStyle; - public bool UsingClosestAnchor { get; set; } = true; + public bool OverridesClosestAnchor { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 944e42ed0e..206e10943d 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -13,7 +13,7 @@ namespace osu.Game.Skinning protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; - public bool UsingClosestAnchor { get; set; } = true; + public bool OverridesClosestAnchor { get; set; } public LegacyScoreCounter() : base(6) diff --git a/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs b/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs index 6acd917991..b661a02ca5 100644 --- a/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Skinning { public bool IsEditable => false; - public bool UsingClosestAnchor { get; set; } = true; + public bool OverridesClosestAnchor { get; set; } private readonly Action applyDefaults; From 29fa4fdf573176d68dda7996b8ccd6b5314caa0c Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Mon, 7 Jun 2021 01:08:39 -0400 Subject: [PATCH 219/520] Refactor unacceptable syntax --- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 3 +- .../Skinning/Editor/SkinSelectionHandler.cs | 30 ++++++++++++------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 207bea77ce..1549ba47e0 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -56,7 +56,8 @@ namespace osu.Game.Screens.Play.HUD Anchor = component.Anchor; Origin = component.Origin; - OverridesClosestAnchor = component is ISkinnableDrawable { OverridesClosestAnchor: true }; + if (component is ISkinnableDrawable skinnable) + OverridesClosestAnchor = skinnable.OverridesClosestAnchor; if (component is Container container) { diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 7ddcebd662..74609a6a1a 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -233,15 +233,17 @@ namespace osu.Game.Skinning.Editor Drawable drawable = (Drawable)c.Item; drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); - updateDrawableAnchorIfUsingClosest(drawable); + updateDrawableAnchorIfUsingClosest(c.Item); } return true; } - private void updateDrawableAnchorIfUsingClosest(Drawable drawable) + private void updateDrawableAnchorIfUsingClosest(ISkinnableDrawable item) { - if (drawable is ISkinnableDrawable { OverridesClosestAnchor: true }) return; + if (item.OverridesClosestAnchor) return; + + var drawable = (Drawable)item; var closestAnchor = getClosestAnchorForDrawable(drawable); @@ -265,10 +267,16 @@ namespace osu.Game.Skinning.Editor protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { - static int checkAnchor(Drawable drawable) => - drawable is ISkinnableDrawable { OverridesClosestAnchor: true } - ? (int)drawable.Anchor - : closest_text_hash; + static int checkAnchor(ISkinnableDrawable item) + { + if (item.OverridesClosestAnchor) + { + var drawable = (Drawable)item; + return (int)drawable.Anchor; + } + + return closest_text_hash; + } yield return new OsuMenuItem(nameof(Anchor)) { @@ -278,20 +286,20 @@ namespace osu.Game.Skinning.Editor yield return new OsuMenuItem(nameof(Origin)) { // Origins can't be "closest" so we just cast to int - Items = createAnchorItems(origin_menu_items, d => (int)d.Origin, applyOrigin).ToArray() + Items = createAnchorItems(origin_menu_items, d => (int)((Drawable)d).Origin, applyOrigin).ToArray() }; foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; - IEnumerable createAnchorItems(IDictionary items, Func checkFunction, Action applyFunction) => + IEnumerable createAnchorItems(IDictionary items, Func checkFunction, Action applyFunction) => items.Select(pair => { var (hash, text) = pair; return new TernaryStateRadioMenuItem(text, MenuItemType.Standard, _ => applyFunction(hash)) { - State = { Value = GetStateFromSelection(selection, c => checkFunction((Drawable)c.Item) == hash) } + State = { Value = GetStateFromSelection(selection, c => checkFunction(c.Item) == hash) } }; }); } @@ -314,7 +322,7 @@ namespace osu.Game.Skinning.Editor drawable.Origin = anchor; drawable.Position += drawable.OriginPosition - previousOrigin; - updateDrawableAnchorIfUsingClosest(drawable); + updateDrawableAnchorIfUsingClosest(item); } } From 6c9594ee350a1710cc9a9eb783a8a9bad27feff6 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Mon, 7 Jun 2021 02:40:15 -0400 Subject: [PATCH 220/520] Simplify and rearrange SkinSelectionHandler The file has been restructured and reworded such that there are as few differences as possible from b36b40cb3430e6bd511390f82c39ba23195dc8cb. --- .../Skinning/Editor/SkinSelectionHandler.cs | 205 ++++++++---------- 1 file changed, 90 insertions(+), 115 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 74609a6a1a..b7fe5beb5c 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -20,87 +20,6 @@ namespace osu.Game.Skinning.Editor { public class SkinSelectionHandler : SelectionHandler { - private const string closest_text = @"Closest"; - - /// - /// The hash code of the "Closest" menu item in the anchor point context menu. - /// - /// Needs only be constant and distinct from any values. - private static readonly int closest_text_hash = closest_text.GetHashCode(); - - private static readonly Dictionary anchor_menu_items; - private static readonly Dictionary origin_menu_items; - - static SkinSelectionHandler() - { - var anchorMenuSubset = new[] - { - Anchor.TopLeft, - Anchor.TopCentre, - Anchor.TopRight, - Anchor.CentreLeft, - Anchor.Centre, - Anchor.CentreRight, - Anchor.BottomLeft, - Anchor.BottomCentre, - Anchor.BottomRight, - }; - - var texts = anchorMenuSubset.Select(a => a.ToString()).Prepend(closest_text).ToArray(); - var hashes = anchorMenuSubset.Cast().Prepend(closest_text_hash).ToArray(); - - var anchorPairs = hashes.Zip(texts, (k, v) => new KeyValuePair(k, v)).ToArray(); - anchor_menu_items = new Dictionary(anchorPairs); - var originPairs = anchorPairs.Where(pair => pair.Key != closest_text_hash); - origin_menu_items = new Dictionary(originPairs); - } - - private Anchor getClosestAnchorForDrawable(Drawable drawable) - { - var parent = drawable.Parent; - if (parent == null) - return drawable.Anchor; - - var screenPosition = getOriginPositionFromQuad(drawable.ScreenSpaceDrawQuad, drawable.Origin); - var absolutePosition = parent.ToLocalSpace(screenPosition); - var factor = parent.RelativeToAbsoluteFactor; - - var result = default(Anchor); - - result |= getTieredComponent(absolutePosition.X / factor.X, Anchor.x0, Anchor.x1, Anchor.x2); - result |= getTieredComponent(absolutePosition.Y / factor.Y, Anchor.y0, Anchor.y1, Anchor.y2); - - return result; - } - - private static Anchor getTieredComponent(float component, Anchor tier0, Anchor tier1, Anchor tier2) - { - if (component >= 2 / 3f) - return tier2; - - if (component >= 1 / 3f) - return tier1; - - return tier0; - } - - private Vector2 getOriginPositionFromQuad(in Quad quad, Anchor origin) - { - var result = quad.TopLeft; - - if (origin.HasFlagFast(Anchor.x2)) - result.X += quad.Width; - else if (origin.HasFlagFast(Anchor.x1)) - result.X += quad.Width / 2f; - - if (origin.HasFlagFast(Anchor.y2)) - result.Y += quad.Height; - else if (origin.HasFlagFast(Anchor.y1)) - result.Y += quad.Height / 2f; - - return result; - } - [Resolved] private SkinEditor skinEditor { get; set; } @@ -267,41 +186,48 @@ namespace osu.Game.Skinning.Editor protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { - static int checkAnchor(ISkinnableDrawable item) + var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchor()) { - if (item.OverridesClosestAnchor) - { - var drawable = (Drawable)item; - return (int)drawable.Anchor; - } - - return closest_text_hash; - } - - yield return new OsuMenuItem(nameof(Anchor)) - { - Items = createAnchorItems(anchor_menu_items, checkAnchor, applyAnchor).ToArray() + State = { Value = GetStateFromSelection(selection, c => !c.Item.OverridesClosestAnchor) } }; - yield return new OsuMenuItem(nameof(Origin)) + yield return new OsuMenuItem("Anchor") { - // Origins can't be "closest" so we just cast to int - Items = createAnchorItems(origin_menu_items, d => (int)((Drawable)d).Origin, applyOrigin).ToArray() + Items = createAnchorItems((i, a) => i.OverridesClosestAnchor && ((Drawable)i).Anchor == a, applyAnchor) + .Prepend(closestItem) + .ToArray() + }; + + yield return new OsuMenuItem("Origin") + { + Items = createAnchorItems((i, o) => ((Drawable)i).Origin == o, applyOrigin).ToArray() }; foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; - IEnumerable createAnchorItems(IDictionary items, Func checkFunction, Action applyFunction) => - items.Select(pair => + IEnumerable createAnchorItems(Func checkFunction, Action applyFunction) + { + var displayableAnchors = new[] { - var (hash, text) = pair; - - return new TernaryStateRadioMenuItem(text, MenuItemType.Standard, _ => applyFunction(hash)) + Anchor.TopLeft, + Anchor.TopCentre, + Anchor.TopRight, + Anchor.CentreLeft, + Anchor.Centre, + Anchor.CentreRight, + Anchor.BottomLeft, + Anchor.BottomCentre, + Anchor.BottomRight, + }; + return displayableAnchors.Select(a => + { + return new TernaryStateRadioMenuItem(a.ToString(), MenuItemType.Standard, _ => applyFunction(a)) { - State = { Value = GetStateFromSelection(selection, c => checkFunction(c.Item) == hash) } + State = { Value = GetStateFromSelection(selection, c => checkFunction(c.Item, a)) } }; }); + } } private static void updateDrawablePosition(Drawable drawable, Vector2 screenSpacePosition) @@ -310,10 +236,8 @@ namespace osu.Game.Skinning.Editor drawable.Parent.ToLocalSpace(screenSpacePosition) - drawable.AnchorPosition; } - private void applyOrigin(int hash) + private void applyOrigin(Anchor anchor) { - var anchor = (Anchor)hash; - foreach (var item in SelectedItems) { var drawable = (Drawable)item; @@ -321,8 +245,6 @@ namespace osu.Game.Skinning.Editor var previousOrigin = drawable.OriginPosition; drawable.Origin = anchor; drawable.Position += drawable.OriginPosition - previousOrigin; - - updateDrawableAnchorIfUsingClosest(item); } } @@ -333,22 +255,75 @@ namespace osu.Game.Skinning.Editor private Quad getSelectionQuad() => GetSurroundingQuad(SelectedBlueprints.SelectMany(b => b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); - private void applyAnchor(int hash) + private void applyAnchor(Anchor anchor) { foreach (var item in SelectedItems) { var drawable = (Drawable)item; - var (overridesClosest, anchor) = - hash == closest_text_hash - ? (false, getClosestAnchorForDrawable(drawable)) - : (true, (Anchor)hash); - - item.OverridesClosestAnchor = overridesClosest; + item.OverridesClosestAnchor = true; updateDrawableAnchor(drawable, anchor); } } + private void applyClosestAnchor() + { + foreach (var item in SelectedItems) + { + var drawable = (Drawable)item; + + item.OverridesClosestAnchor = false; + updateDrawableAnchor(drawable, getClosestAnchorForDrawable(drawable)); + } + } + + private static Anchor getClosestAnchorForDrawable(Drawable drawable) + { + var parent = drawable.Parent; + + if (parent == null) + return drawable.Anchor; + + var screenPosition = getOriginPositionFromQuad(drawable.ScreenSpaceDrawQuad, drawable.Origin); + var absolutePosition = parent.ToLocalSpace(screenPosition); + var factor = parent.RelativeToAbsoluteFactor; + + var result = default(Anchor); + + result |= getTieredComponent(absolutePosition.X / factor.X, Anchor.x0, Anchor.x1, Anchor.x2); + result |= getTieredComponent(absolutePosition.Y / factor.Y, Anchor.y0, Anchor.y1, Anchor.y2); + + return result; + } + + private static Vector2 getOriginPositionFromQuad(in Quad quad, Anchor origin) + { + var result = quad.TopLeft; + + if (origin.HasFlagFast(Anchor.x2)) + result.X += quad.Width; + else if (origin.HasFlagFast(Anchor.x1)) + result.X += quad.Width / 2f; + + if (origin.HasFlagFast(Anchor.y2)) + result.Y += quad.Height; + else if (origin.HasFlagFast(Anchor.y1)) + result.Y += quad.Height / 2f; + + return result; + } + + private static Anchor getTieredComponent(float component, Anchor tier0, Anchor tier1, Anchor tier2) + { + if (component >= 2 / 3f) + return tier2; + + if (component >= 1 / 3f) + return tier1; + + return tier0; + } + private static void updateDrawableAnchor(Drawable drawable, Anchor anchor) { var previousAnchor = drawable.AnchorPosition; From 65f594f860b5e0be75e0c4b4eb09d50e859b6ba8 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Mon, 7 Jun 2021 05:08:18 -0400 Subject: [PATCH 221/520] Rename `applyAnchor` to `applyCustomAnchor` --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index b7fe5beb5c..118c3308b2 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -193,7 +193,7 @@ namespace osu.Game.Skinning.Editor yield return new OsuMenuItem("Anchor") { - Items = createAnchorItems((i, a) => i.OverridesClosestAnchor && ((Drawable)i).Anchor == a, applyAnchor) + Items = createAnchorItems((i, a) => i.OverridesClosestAnchor && ((Drawable)i).Anchor == a, applyCustomAnchor) .Prepend(closestItem) .ToArray() }; @@ -255,7 +255,7 @@ namespace osu.Game.Skinning.Editor private Quad getSelectionQuad() => GetSurroundingQuad(SelectedBlueprints.SelectMany(b => b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); - private void applyAnchor(Anchor anchor) + private void applyCustomAnchor(Anchor anchor) { foreach (var item in SelectedItems) { From f1bef989b748476ab0914f573f1e05754b579778 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Tue, 8 Jun 2021 19:43:59 +1000 Subject: [PATCH 222/520] Refactor DifficultyAttributes to use auto properties over public fields --- .../Difficulty/CatchDifficultyAttributes.cs | 2 +- .../Difficulty/ManiaDifficultyAttributes.cs | 4 ++-- .../Difficulty/OsuDifficultyAttributes.cs | 12 ++++++------ .../Difficulty/TaikoDifficultyAttributes.cs | 10 +++++----- osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 8 ++++---- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index fa9011d826..4e05b1e3e0 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty { public class CatchDifficultyAttributes : DifficultyAttributes { - public double ApproachRate; + public double ApproachRate { get; set; } } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 0b58d1efc6..628d77107f 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty { public class ManiaDifficultyAttributes : DifficultyAttributes { - public double GreatHitWindow; - public double ScoreMultiplier; + public double GreatHitWindow { get; set; } + public double ScoreMultiplier { get; set; } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index e8ac60bc5e..141138c125 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -7,11 +7,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuDifficultyAttributes : DifficultyAttributes { - public double AimStrain; - public double SpeedStrain; - public double ApproachRate; - public double OverallDifficulty; - public int HitCircleCount; - public int SpinnerCount; + public double AimStrain { get; set; } + public double SpeedStrain { get; set; } + public double ApproachRate { get; set; } + public double OverallDifficulty { get; set; } + public int HitCircleCount { get; set; } + public int SpinnerCount { get; set; } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index 5bed48bcc6..36adbd5a5b 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -7,10 +7,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { public class TaikoDifficultyAttributes : DifficultyAttributes { - public double StaminaStrain; - public double RhythmStrain; - public double ColourStrain; - public double ApproachRate; - public double GreatHitWindow; + public double StaminaStrain { get; set; } + public double RhythmStrain { get; set; } + public double ColourStrain { get; set; } + public double ApproachRate { get; set; } + public double GreatHitWindow { get; set; } } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index 732dc772b7..6bb780a68b 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -8,11 +8,11 @@ namespace osu.Game.Rulesets.Difficulty { public class DifficultyAttributes { - public Mod[] Mods; - public Skill[] Skills; + public Mod[] Mods { get; set; } + public Skill[] Skills { get; set; } - public double StarRating; - public int MaxCombo; + public double StarRating { get; set; } + public int MaxCombo { get; set; } public DifficultyAttributes() { From dc50ae40b9b604785c1093c3c5cdc9dbb6160876 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Tue, 8 Jun 2021 08:22:35 -0400 Subject: [PATCH 223/520] Rename `OverridesClosestAnchor` to `UsesFixedAnchor` --- osu.Game/Extensions/DrawableExtensions.cs | 2 +- osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs | 2 +- osu.Game/Screens/Play/HUD/DefaultComboCounter.cs | 2 +- osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs | 2 +- osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs | 2 +- .../Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs | 2 +- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 2 +- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 6 +++--- osu.Game/Screens/Play/SongProgress.cs | 2 +- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 10 +++++----- osu.Game/Skinning/ISkinnableDrawable.cs | 4 ++-- osu.Game/Skinning/LegacyAccuracyCounter.cs | 2 +- osu.Game/Skinning/LegacyHealthDisplay.cs | 2 +- osu.Game/Skinning/LegacyScoreCounter.cs | 2 +- .../Skinning/SkinnableTargetComponentsContainer.cs | 2 +- 15 files changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 11a8112ecf..52d8230fb6 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -59,7 +59,7 @@ namespace osu.Game.Extensions component.Origin = info.Origin; if (component is ISkinnableDrawable skinnable) - skinnable.OverridesClosestAnchor = info.OverridesClosestAnchor; + skinnable.UsesFixedAnchor = info.UsesFixedAnchor; if (component is Container container) { diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs index 31edab5bc2..324e5d43b5 100644 --- a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Play.HUD [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - public bool OverridesClosestAnchor { get; set; } + public bool UsesFixedAnchor { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index bf9bed3fe3..718ae24cf1 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - public bool OverridesClosestAnchor { get; set; } + public bool UsesFixedAnchor { get; set; } public DefaultComboCounter() { diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index 3488de70a9..4f93868a66 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Play.HUD } } - public bool OverridesClosestAnchor { get; set; } + public bool UsesFixedAnchor { get; set; } public DefaultHealthDisplay() { diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs index de534f516a..63de5c8de5 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Play.HUD [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - public bool OverridesClosestAnchor { get; set; } + public bool UsesFixedAnchor { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index a16adfebbc..ab8b5c6cb1 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [Resolved] private OsuColour colours { get; set; } - public bool OverridesClosestAnchor { get; set; } + public bool UsesFixedAnchor { get; set; } [BackgroundDependencyLoader(true)] private void load(DrawableRuleset drawableRuleset) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index aff98b843b..acff949353 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Play.HUD set => counterContainer.Alpha = value ? 1 : 0; } - public bool OverridesClosestAnchor { get; set; } + public bool UsesFixedAnchor { get; set; } public LegacyComboCounter() { diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 1549ba47e0..b64e5ca98f 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -32,8 +32,8 @@ namespace osu.Game.Screens.Play.HUD public Anchor Origin { get; set; } - /// - public bool OverridesClosestAnchor { get; set; } + /// + public bool UsesFixedAnchor { get; set; } public List Children { get; } = new List(); @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Play.HUD Origin = component.Origin; if (component is ISkinnableDrawable skinnable) - OverridesClosestAnchor = skinnable.OverridesClosestAnchor; + UsesFixedAnchor = skinnable.UsesFixedAnchor; if (component is Container container) { diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index a00a2c0275..bd861dc598 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play private IClock referenceClock; - public bool OverridesClosestAnchor { get; set; } + public bool UsesFixedAnchor { get; set; } public SongProgress() { diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 118c3308b2..78173bfda1 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -160,7 +160,7 @@ namespace osu.Game.Skinning.Editor private void updateDrawableAnchorIfUsingClosest(ISkinnableDrawable item) { - if (item.OverridesClosestAnchor) return; + if (item.UsesFixedAnchor) return; var drawable = (Drawable)item; @@ -188,12 +188,12 @@ namespace osu.Game.Skinning.Editor { var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchor()) { - State = { Value = GetStateFromSelection(selection, c => !c.Item.OverridesClosestAnchor) } + State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) } }; yield return new OsuMenuItem("Anchor") { - Items = createAnchorItems((i, a) => i.OverridesClosestAnchor && ((Drawable)i).Anchor == a, applyCustomAnchor) + Items = createAnchorItems((i, a) => i.UsesFixedAnchor && ((Drawable)i).Anchor == a, applyCustomAnchor) .Prepend(closestItem) .ToArray() }; @@ -261,7 +261,7 @@ namespace osu.Game.Skinning.Editor { var drawable = (Drawable)item; - item.OverridesClosestAnchor = true; + item.UsesFixedAnchor = true; updateDrawableAnchor(drawable, anchor); } } @@ -272,7 +272,7 @@ namespace osu.Game.Skinning.Editor { var drawable = (Drawable)item; - item.OverridesClosestAnchor = false; + item.UsesFixedAnchor = false; updateDrawableAnchor(drawable, getClosestAnchorForDrawable(drawable)); } } diff --git a/osu.Game/Skinning/ISkinnableDrawable.cs b/osu.Game/Skinning/ISkinnableDrawable.cs index 1b5c0df1b2..9625a9eb6d 100644 --- a/osu.Game/Skinning/ISkinnableDrawable.cs +++ b/osu.Game/Skinning/ISkinnableDrawable.cs @@ -17,8 +17,8 @@ namespace osu.Game.Skinning /// /// if this 's is automatically determined by proximity, - /// if the user has overridden it. + /// if the user has chosen a fixed anchor point. /// - bool OverridesClosestAnchor { get; set; } + bool UsesFixedAnchor { get; set; } } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 603f0ecffb..fd5a9500d9 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -12,7 +12,7 @@ namespace osu.Game.Skinning { public class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable { - public bool OverridesClosestAnchor { get; set; } + public bool UsesFixedAnchor { get; set; } public LegacyAccuracyCounter() { diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index a419f981b5..c8df99762b 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Skinning private bool isNewStyle; - public bool OverridesClosestAnchor { get; set; } + public bool UsesFixedAnchor { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 206e10943d..a12defe87e 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -13,7 +13,7 @@ namespace osu.Game.Skinning protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; - public bool OverridesClosestAnchor { get; set; } + public bool UsesFixedAnchor { get; set; } public LegacyScoreCounter() : base(6) diff --git a/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs b/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs index b661a02ca5..67114de948 100644 --- a/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Skinning { public bool IsEditable => false; - public bool OverridesClosestAnchor { get; set; } + public bool UsesFixedAnchor { get; set; } private readonly Action applyDefaults; From 6b127f50f21afb89a5bbc2ff8f5364a4bd1f9763 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Tue, 8 Jun 2021 09:14:04 -0400 Subject: [PATCH 224/520] Inline updateDrawableAnchorIfUsingClosest --- .../Skinning/Editor/SkinSelectionHandler.cs | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 78173bfda1..7c904d5007 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -152,25 +152,18 @@ namespace osu.Game.Skinning.Editor Drawable drawable = (Drawable)c.Item; drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); - updateDrawableAnchorIfUsingClosest(c.Item); + if (c.Item.UsesFixedAnchor) continue; + + var closestAnchor = getClosestAnchorForDrawable(drawable); + + if (closestAnchor == drawable.Anchor) continue; + + updateDrawableAnchor(drawable, closestAnchor); } return true; } - private void updateDrawableAnchorIfUsingClosest(ISkinnableDrawable item) - { - if (item.UsesFixedAnchor) return; - - var drawable = (Drawable)item; - - var closestAnchor = getClosestAnchorForDrawable(drawable); - - if (closestAnchor == drawable.Anchor) return; - - updateDrawableAnchor(drawable, closestAnchor); - } - protected override void OnSelectionChanged() { base.OnSelectionChanged(); From 01da73daf211047392b85717621dba921d09d7b5 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Tue, 8 Jun 2021 09:25:49 -0400 Subject: [PATCH 225/520] Refactor `updateDrawableAnchorIfUsingClosest` --- .../Skinning/Editor/SkinSelectionHandler.cs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 7c904d5007..4aa6e79fb8 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -149,21 +149,29 @@ namespace osu.Game.Skinning.Editor { foreach (var c in SelectedBlueprints) { - Drawable drawable = (Drawable)c.Item; + var skinnableDrawable = c.Item; + Drawable drawable = (Drawable)skinnableDrawable; drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); - if (c.Item.UsesFixedAnchor) continue; - - var closestAnchor = getClosestAnchorForDrawable(drawable); - - if (closestAnchor == drawable.Anchor) continue; - - updateDrawableAnchor(drawable, closestAnchor); + checkAndApplyClosestAnchor(skinnableDrawable); } return true; } + private static void checkAndApplyClosestAnchor(ISkinnableDrawable item) + { + if (item.UsesFixedAnchor) return; + + var drawable = (Drawable)item; + + var closestAnchor = getClosestAnchorForDrawable(drawable); + + if (closestAnchor == drawable.Anchor) return; + + updateDrawableAnchor(drawable, closestAnchor); + } + protected override void OnSelectionChanged() { base.OnSelectionChanged(); @@ -263,10 +271,8 @@ namespace osu.Game.Skinning.Editor { foreach (var item in SelectedItems) { - var drawable = (Drawable)item; - item.UsesFixedAnchor = false; - updateDrawableAnchor(drawable, getClosestAnchorForDrawable(drawable)); + checkAndApplyClosestAnchor(item); } } From 529a80871b3faaae3ae04a8787a7756d1287c48f Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Tue, 8 Jun 2021 09:44:42 -0400 Subject: [PATCH 226/520] Rename some methods for clarity Methods which operate on a collection of `ISkinnableDrawable`s are now plural; ones which take a single item are singular. This also allows cutting down the name of `getClosestAnchorForDrawable` to just `getClosestAnchor`. --- .../Skinning/Editor/SkinSelectionHandler.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 4aa6e79fb8..206f39da5c 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -165,11 +165,11 @@ namespace osu.Game.Skinning.Editor var drawable = (Drawable)item; - var closestAnchor = getClosestAnchorForDrawable(drawable); + var closestAnchor = getClosestAnchor(drawable); if (closestAnchor == drawable.Anchor) return; - updateDrawableAnchor(drawable, closestAnchor); + applyAnchor(drawable, closestAnchor); } protected override void OnSelectionChanged() @@ -187,21 +187,21 @@ namespace osu.Game.Skinning.Editor protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { - var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchor()) + var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchors()) { State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) } }; yield return new OsuMenuItem("Anchor") { - Items = createAnchorItems((i, a) => i.UsesFixedAnchor && ((Drawable)i).Anchor == a, applyCustomAnchor) + Items = createAnchorItems((i, a) => i.UsesFixedAnchor && ((Drawable)i).Anchor == a, applyCustomAnchors) .Prepend(closestItem) .ToArray() }; yield return new OsuMenuItem("Origin") { - Items = createAnchorItems((i, o) => ((Drawable)i).Origin == o, applyOrigin).ToArray() + Items = createAnchorItems((i, o) => ((Drawable)i).Origin == o, applyOrigins).ToArray() }; foreach (var item in base.GetContextMenuItemsForSelection(selection)) @@ -237,7 +237,7 @@ namespace osu.Game.Skinning.Editor drawable.Parent.ToLocalSpace(screenSpacePosition) - drawable.AnchorPosition; } - private void applyOrigin(Anchor anchor) + private void applyOrigins(Anchor anchor) { foreach (var item in SelectedItems) { @@ -256,18 +256,18 @@ namespace osu.Game.Skinning.Editor private Quad getSelectionQuad() => GetSurroundingQuad(SelectedBlueprints.SelectMany(b => b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); - private void applyCustomAnchor(Anchor anchor) + private void applyCustomAnchors(Anchor anchor) { foreach (var item in SelectedItems) { var drawable = (Drawable)item; item.UsesFixedAnchor = true; - updateDrawableAnchor(drawable, anchor); + applyAnchor(drawable, anchor); } } - private void applyClosestAnchor() + private void applyClosestAnchors() { foreach (var item in SelectedItems) { @@ -276,7 +276,7 @@ namespace osu.Game.Skinning.Editor } } - private static Anchor getClosestAnchorForDrawable(Drawable drawable) + private static Anchor getClosestAnchor(Drawable drawable) { var parent = drawable.Parent; @@ -323,7 +323,7 @@ namespace osu.Game.Skinning.Editor return tier0; } - private static void updateDrawableAnchor(Drawable drawable, Anchor anchor) + private static void applyAnchor(Drawable drawable, Anchor anchor) { var previousAnchor = drawable.AnchorPosition; drawable.Anchor = anchor; From f22cc981d15554665b54bf72956f7397eb7c42f6 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Tue, 8 Jun 2021 09:51:39 -0400 Subject: [PATCH 227/520] Move guard clause from `checkAndApplyClosestAnchor` to `applyAnchor` --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 206f39da5c..a9b985fda2 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -165,11 +165,7 @@ namespace osu.Game.Skinning.Editor var drawable = (Drawable)item; - var closestAnchor = getClosestAnchor(drawable); - - if (closestAnchor == drawable.Anchor) return; - - applyAnchor(drawable, closestAnchor); + applyAnchor(drawable, getClosestAnchor(drawable)); } protected override void OnSelectionChanged() @@ -325,6 +321,8 @@ namespace osu.Game.Skinning.Editor private static void applyAnchor(Drawable drawable, Anchor anchor) { + if (anchor == drawable.Anchor) return; + var previousAnchor = drawable.AnchorPosition; drawable.Anchor = anchor; drawable.Position -= drawable.AnchorPosition - previousAnchor; From 2c88e6df8de34ac1bc5e637bc4d1564d48699dd1 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Tue, 8 Jun 2021 10:08:02 -0400 Subject: [PATCH 228/520] Simplify `applyClosestAnchor` to one line by moving another guard clause --- .../Skinning/Editor/SkinSelectionHandler.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index a9b985fda2..500020ca46 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -149,24 +149,20 @@ namespace osu.Game.Skinning.Editor { foreach (var c in SelectedBlueprints) { - var skinnableDrawable = c.Item; - Drawable drawable = (Drawable)skinnableDrawable; + var item = c.Item; + Drawable drawable = (Drawable)item; + drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); - checkAndApplyClosestAnchor(skinnableDrawable); + if (item.UsesFixedAnchor) continue; + + applyClosestAnchor(drawable); } return true; } - private static void checkAndApplyClosestAnchor(ISkinnableDrawable item) - { - if (item.UsesFixedAnchor) return; - - var drawable = (Drawable)item; - - applyAnchor(drawable, getClosestAnchor(drawable)); - } + private static void applyClosestAnchor(Drawable drawable) => applyAnchor(drawable, getClosestAnchor(drawable)); protected override void OnSelectionChanged() { @@ -268,7 +264,7 @@ namespace osu.Game.Skinning.Editor foreach (var item in SelectedItems) { item.UsesFixedAnchor = false; - checkAndApplyClosestAnchor(item); + applyClosestAnchor((Drawable)item); } } From d212918d67059ecd111155f93c039cf50435a2a8 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Tue, 8 Jun 2021 10:14:07 -0400 Subject: [PATCH 229/520] Rename `applyCustomAnchors` to `applyFixedAnchors` for consistency with `UsesFixedAnchor` --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 500020ca46..e52b1c68d4 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -186,7 +186,7 @@ namespace osu.Game.Skinning.Editor yield return new OsuMenuItem("Anchor") { - Items = createAnchorItems((i, a) => i.UsesFixedAnchor && ((Drawable)i).Anchor == a, applyCustomAnchors) + Items = createAnchorItems((i, a) => i.UsesFixedAnchor && ((Drawable)i).Anchor == a, applyFixedAnchors) .Prepend(closestItem) .ToArray() }; @@ -248,7 +248,7 @@ namespace osu.Game.Skinning.Editor private Quad getSelectionQuad() => GetSurroundingQuad(SelectedBlueprints.SelectMany(b => b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); - private void applyCustomAnchors(Anchor anchor) + private void applyFixedAnchors(Anchor anchor) { foreach (var item in SelectedItems) { From 10b6b7290923a112dfee58c8a8c820dc32934611 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Tue, 8 Jun 2021 10:29:11 -0400 Subject: [PATCH 230/520] Add guard clause to `applyOrigins` and rename parameter --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index e52b1c68d4..ccfb0cb6e0 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -229,14 +229,16 @@ namespace osu.Game.Skinning.Editor drawable.Parent.ToLocalSpace(screenSpacePosition) - drawable.AnchorPosition; } - private void applyOrigins(Anchor anchor) + private void applyOrigins(Anchor origin) { foreach (var item in SelectedItems) { var drawable = (Drawable)item; + if (origin == drawable.Origin) continue; + var previousOrigin = drawable.OriginPosition; - drawable.Origin = anchor; + drawable.Origin = origin; drawable.Position += drawable.OriginPosition - previousOrigin; } } From 330bb7cb450f346b6cbb64a2a4f262c61624090d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 18:15:45 +0900 Subject: [PATCH 231/520] Remove unnecessary skin lookup logic --- .../Expanded/Accuracy/AccuracyCircle.cs | 123 ++++++++---------- osu.Game/Skinning/DefaultSkin.cs | 28 ---- osu.Game/Skinning/GameplaySkinSamples.cs | 29 ----- osu.Game/Skinning/LegacySkin.cs | 38 ------ 4 files changed, 54 insertions(+), 164 deletions(-) delete mode 100644 osu.Game/Skinning/GameplaySkinSamples.cs diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index af43477e84..2348a0891f 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -8,11 +8,11 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Utils; +using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -110,20 +110,18 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private Container badges; private RankText rankText; - private DrawableSample scoreTickSound; - private DrawableSample badgeTickSound; - private DrawableSample badgeMaxSound; - private DrawableSample swooshUpSound; - private DrawableSample rankImpactSound; - private DrawableSample rankApplauseSound; - private DrawableSample legacySkinApplauseSound; + private PoolableSkinnableSample scoreTickSound; + private PoolableSkinnableSample badgeTickSound; + private PoolableSkinnableSample badgeMaxSound; + private PoolableSkinnableSample swooshUpSound; + private PoolableSkinnableSample rankImpactSound; + private PoolableSkinnableSample rankApplauseSound; private Bindable tickPlaybackRate = new Bindable(); private double lastTickPlaybackTime; private bool isTicking; private readonly bool sfxEnabled; - private bool legacySkin => legacySkinApplauseSound != null; public AccuracyCircle(ScoreInfo score, bool sfxEnabled = false) { @@ -254,62 +252,52 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (sfxEnabled) { - Drawable legacySkinApplause = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.Applause)); + tickPlaybackRate = new Bindable(sfx_score_tick_debounce_rate_start); - if (legacySkinApplause != null) + switch (score.Rank) { - AddInternal(legacySkinApplause); - legacySkinApplauseSound = legacySkinApplause as DrawableSample; + case ScoreRank.D: + rankImpactSound = new PoolableSkinnableSample(new SampleInfo(@"Results/rank-impact-fail-d")); + rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", @"Results/applause-d")); + break; + + case ScoreRank.C: + rankImpactSound = new PoolableSkinnableSample(new SampleInfo(@"Results/rank-impact-fail")); + rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", @"Results/applause-c")); + break; + + case ScoreRank.B: + rankImpactSound = new PoolableSkinnableSample(new SampleInfo(@"Results/rank-impact-fail")); + rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", @"Results/applause-b")); + break; + + case ScoreRank.A: + rankImpactSound = new PoolableSkinnableSample(new SampleInfo(@"Results/rank-impact-pass")); + rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", @"Results/applause-a")); + break; + + case ScoreRank.S: + case ScoreRank.SH: + rankImpactSound = new PoolableSkinnableSample(new SampleInfo(@"Results/rank-impact-pass")); + rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", @"Results/applause-s")); + break; + + case ScoreRank.X: + case ScoreRank.XH: + rankImpactSound = new PoolableSkinnableSample(new SampleInfo(@"Results/rank-impact-pass-ss")); + rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", @"Results/applause-s")); + break; } - else + + AddRangeInternal(new Drawable[] { - tickPlaybackRate = new Bindable(sfx_score_tick_debounce_rate_start); - - switch (score.Rank) - { - case ScoreRank.D: - rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_D)) as DrawableSample; - rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_D)) as DrawableSample; - break; - - case ScoreRank.C: - rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_C)) as DrawableSample; - rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_C)) as DrawableSample; - break; - - case ScoreRank.B: - rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_B)) as DrawableSample; - rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_B)) as DrawableSample; - break; - - case ScoreRank.A: - rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_A)) as DrawableSample; - rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_A)) as DrawableSample; - break; - - case ScoreRank.S: - case ScoreRank.SH: - rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_S)) as DrawableSample; - rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_S)) as DrawableSample; - break; - - case ScoreRank.X: - case ScoreRank.XH: - rankImpactSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultRank_SS)) as DrawableSample; - rankApplauseSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultApplause_SS)) as DrawableSample; - break; - } - - AddRangeInternal(new Drawable[] - { - rankImpactSound, - rankApplauseSound, - scoreTickSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultScoreTick)) as DrawableSample, - badgeTickSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultBadgeTick)) as DrawableSample, - badgeMaxSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultBadgeTickMax)) as DrawableSample, - swooshUpSound = skin.GetDrawableComponent(new GameplaySkinComponent(GameplaySkinSamples.ResultSwooshUp)) as DrawableSample - }); - } + rankImpactSound, + rankApplauseSound, + scoreTickSound = new PoolableSkinnableSample(new SampleInfo(@"Results/score-tick")), + badgeTickSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink")), + badgeMaxSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink-max")), + swooshUpSound = new PoolableSkinnableSample(new SampleInfo(@"Results/swoosh-up")), + }); } } @@ -341,7 +329,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint); - if (sfxEnabled && !legacySkin) + if (sfxEnabled) { this.Delay(sfx_swoosh_pre_delay).Schedule(() => { @@ -359,7 +347,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); - if (sfxEnabled && !legacySkin) + if (sfxEnabled) { Schedule(() => { @@ -382,11 +370,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { badge.Appear(); - if (sfxEnabled && !legacySkin) + if (sfxEnabled) { Schedule(() => { - DrawableSample dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound; + var dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound; + dink.FrequencyTo(1 + badgeNum++ * 0.05); dink.VolumeTo(sfx_badge_dink_volume); dink.Play(); @@ -401,10 +390,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (!sfxEnabled) return; - legacySkinApplauseSound?.Play(); - - if (legacySkin) return; - Schedule(() => { isTicking = false; diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 46a8fef8d2..893819b2c2 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -7,7 +7,6 @@ using JetBrains.Annotations; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; @@ -25,27 +24,6 @@ namespace osu.Game.Skinning { private readonly IStorageResourceProvider resources; - private static readonly IReadOnlyDictionary sample_mapping - = new Dictionary - { - { GameplaySkinSamples.ResultScoreTick, @"Results/score-tick" }, - { GameplaySkinSamples.ResultBadgeTick, @"Results/badge-dink" }, - { GameplaySkinSamples.ResultBadgeTickMax, @"Results/badge-dink-max" }, - { GameplaySkinSamples.ResultSwooshUp, @"Results/swoosh-up" }, - { GameplaySkinSamples.ResultRank_D, @"Results/rank-impact-fail-d" }, - { GameplaySkinSamples.ResultRank_B, @"Results/rank-impact-fail" }, - { GameplaySkinSamples.ResultRank_C, @"Results/rank-impact-fail" }, - { GameplaySkinSamples.ResultRank_A, @"Results/rank-impact-pass" }, - { GameplaySkinSamples.ResultRank_S, @"Results/rank-impact-pass" }, - { GameplaySkinSamples.ResultRank_SS, @"Results/rank-impact-pass-ss" }, - { GameplaySkinSamples.ResultApplause_D, @"Results/applause-d" }, - { GameplaySkinSamples.ResultApplause_B, @"Results/applause-b" }, - { GameplaySkinSamples.ResultApplause_C, @"Results/applause-c" }, - { GameplaySkinSamples.ResultApplause_A, @"Results/applause-a" }, - { GameplaySkinSamples.ResultApplause_S, @"Results/applause-s" }, - { GameplaySkinSamples.ResultApplause_SS, @"Results/applause-s" } - }; - public DefaultSkin(IStorageResourceProvider resources) : this(SkinInfo.Default, resources) { @@ -80,12 +58,6 @@ namespace osu.Game.Skinning switch (component) { - case GameplaySkinComponent sample: - if (sample_mapping.ContainsKey(sample.Component)) - return new DrawableSample(GetSample(new SampleInfo(sample_mapping[sample.Component]))); - - break; - case SkinnableTargetComponent target: switch (target.Target) { diff --git a/osu.Game/Skinning/GameplaySkinSamples.cs b/osu.Game/Skinning/GameplaySkinSamples.cs deleted file mode 100644 index 895e95e0a9..0000000000 --- a/osu.Game/Skinning/GameplaySkinSamples.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Skinning -{ - public enum GameplaySkinSamples - { - // legacy - Applause, - - // results screen - ResultScoreTick, - ResultBadgeTick, - ResultBadgeTickMax, - ResultSwooshUp, - ResultRank_D, - ResultRank_B, - ResultRank_C, - ResultRank_A, - ResultRank_S, - ResultRank_SS, - ResultApplause_D, - ResultApplause_B, - ResultApplause_C, - ResultApplause_A, - ResultApplause_S, - ResultApplause_SS - } -} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 38ae65d133..e255fbae81 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -10,7 +10,6 @@ using JetBrains.Annotations; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; @@ -397,43 +396,6 @@ namespace osu.Game.Skinning return null; - case GameplaySkinComponent sampleComponent: - var applause = GetSample(new SampleInfo("applause")); - - switch (sampleComponent.Component) - { - case GameplaySkinSamples.Applause: - if (applause != null) - return new DrawableSample(applause); - - break; - - case GameplaySkinSamples.ResultScoreTick: - case GameplaySkinSamples.ResultBadgeTick: - case GameplaySkinSamples.ResultBadgeTickMax: - case GameplaySkinSamples.ResultSwooshUp: - case GameplaySkinSamples.ResultRank_D: - case GameplaySkinSamples.ResultRank_B: - case GameplaySkinSamples.ResultRank_C: - case GameplaySkinSamples.ResultRank_A: - case GameplaySkinSamples.ResultRank_S: - case GameplaySkinSamples.ResultRank_SS: - case GameplaySkinSamples.ResultApplause_D: - case GameplaySkinSamples.ResultApplause_B: - case GameplaySkinSamples.ResultApplause_C: - case GameplaySkinSamples.ResultApplause_A: - case GameplaySkinSamples.ResultApplause_S: - case GameplaySkinSamples.ResultApplause_SS: - if (applause != null) - // Legacy skins don't have sounds for the result screen, but may instead have an 'applause' sound. - // This lets a legacy skin's applause sound play instead of result screen sounds (as to not play over each other) - return Drawable.Empty(); - - break; - } - - break; - case GameplaySkinComponent resultComponent: // TODO: this should be inside the judgement pieces. Func createDrawable = () => getJudgementAnimation(resultComponent.Component); From 21a63efd7808a4c91647cc98846835f464890993 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 18:46:38 +0900 Subject: [PATCH 232/520] Rename variable back to `withFlair` to match parent class --- .../Ranking/Expanded/Accuracy/AccuracyCircle.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 2348a0891f..6ef509f40a 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -121,12 +121,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private double lastTickPlaybackTime; private bool isTicking; - private readonly bool sfxEnabled; + private readonly bool withFlair; - public AccuracyCircle(ScoreInfo score, bool sfxEnabled = false) + public AccuracyCircle(ScoreInfo score, bool withFlair = false) { this.score = score; - this.sfxEnabled = sfxEnabled; + this.withFlair = withFlair; } [BackgroundDependencyLoader] @@ -250,7 +250,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy rankText = new RankText(score.Rank) }; - if (sfxEnabled) + if (withFlair) { tickPlaybackRate = new Bindable(sfx_score_tick_debounce_rate_start); @@ -329,7 +329,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint); - if (sfxEnabled) + if (withFlair) { this.Delay(sfx_swoosh_pre_delay).Schedule(() => { @@ -347,7 +347,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); - if (sfxEnabled) + if (withFlair) { Schedule(() => { @@ -370,7 +370,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { badge.Appear(); - if (sfxEnabled) + if (withFlair) { Schedule(() => { @@ -388,7 +388,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { rankText.Appear(); - if (!sfxEnabled) return; + if (!withFlair) return; Schedule(() => { From 489a5a3c1d58c96985b4ef2c16ac261f90e19254 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 19:01:05 +0900 Subject: [PATCH 233/520] Add back missing space in csproj --- 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 ae3644d5cb..c80c58b87c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From 499aba95c09ade2ec85d8ed0a1a0bda41c1c0dee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 19:06:37 +0900 Subject: [PATCH 234/520] Simplify sample construction logic and move private functions down --- .../Expanded/Accuracy/AccuracyCircle.cs | 105 +++++++++++------- 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 6ef509f40a..b8afdb98b2 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -254,45 +254,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { tickPlaybackRate = new Bindable(sfx_score_tick_debounce_rate_start); - switch (score.Rank) - { - case ScoreRank.D: - rankImpactSound = new PoolableSkinnableSample(new SampleInfo(@"Results/rank-impact-fail-d")); - rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", @"Results/applause-d")); - break; - - case ScoreRank.C: - rankImpactSound = new PoolableSkinnableSample(new SampleInfo(@"Results/rank-impact-fail")); - rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", @"Results/applause-c")); - break; - - case ScoreRank.B: - rankImpactSound = new PoolableSkinnableSample(new SampleInfo(@"Results/rank-impact-fail")); - rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", @"Results/applause-b")); - break; - - case ScoreRank.A: - rankImpactSound = new PoolableSkinnableSample(new SampleInfo(@"Results/rank-impact-pass")); - rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", @"Results/applause-a")); - break; - - case ScoreRank.S: - case ScoreRank.SH: - rankImpactSound = new PoolableSkinnableSample(new SampleInfo(@"Results/rank-impact-pass")); - rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", @"Results/applause-s")); - break; - - case ScoreRank.X: - case ScoreRank.XH: - rankImpactSound = new PoolableSkinnableSample(new SampleInfo(@"Results/rank-impact-pass-ss")); - rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", @"Results/applause-s")); - break; - } - AddRangeInternal(new Drawable[] { - rankImpactSound, - rankApplauseSound, + rankImpactSound = new PoolableSkinnableSample(new SampleInfo(impactSampleName)), + rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", applauseSampleName)), scoreTickSound = new PoolableSkinnableSample(new SampleInfo(@"Results/score-tick")), badgeTickSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink")), badgeMaxSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink-max")), @@ -301,12 +266,32 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } } - private ScoreRank getRank(ScoreRank rank) + private string applauseSampleName { - foreach (var mod in score.Mods.OfType()) - rank = mod.AdjustRank(rank, score.Accuracy); + get + { + switch (score.Rank) + { + default: + case ScoreRank.D: + return @"Results/rank-applause-d"; - return rank; + case ScoreRank.C: + return @"Results/rank-applause-c"; + + case ScoreRank.B: + return @"Results/rank-applause-b"; + + case ScoreRank.A: + return @"Results/rank-applause-a"; + + case ScoreRank.S: + case ScoreRank.SH: + case ScoreRank.X: + case ScoreRank.XH: + return @"Results/rank-applause-s"; + } + } } protected override void Update() @@ -409,6 +394,44 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } } + private string impactSampleName + { + get + { + switch (score.Rank) + { + default: + case ScoreRank.D: + return @"Results/rank-impact-fail-d"; + + case ScoreRank.C: + return @"Results/rank-impact-fail"; + + case ScoreRank.B: + return @"Results/rank-impact-fail"; + + case ScoreRank.A: + return @"Results/rank-impact-pass"; + + case ScoreRank.S: + case ScoreRank.SH: + return @"Results/rank-impact-pass"; + + case ScoreRank.X: + case ScoreRank.XH: + return @"Results/rank-impact-pass-ss"; + } + } + } + + private ScoreRank getRank(ScoreRank rank) + { + foreach (var mod in score.Mods.OfType()) + rank = mod.AdjustRank(rank, score.Accuracy); + + return rank; + } + private double inverseEasing(Easing easing, double targetValue) { double test = 0; From 81cecac90b05244025eee59e0be4f019bedd9a60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 19:09:00 +0900 Subject: [PATCH 235/520] Move tick rate initialisation to earlier --- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index b8afdb98b2..f62f8bfb72 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -117,7 +117,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private PoolableSkinnableSample rankImpactSound; private PoolableSkinnableSample rankApplauseSound; - private Bindable tickPlaybackRate = new Bindable(); + private readonly Bindable tickPlaybackRate = new Bindable(sfx_score_tick_debounce_rate_start); + private double lastTickPlaybackTime; private bool isTicking; @@ -252,8 +253,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (withFlair) { - tickPlaybackRate = new Bindable(sfx_score_tick_debounce_rate_start); - AddRangeInternal(new Drawable[] { rankImpactSound = new PoolableSkinnableSample(new SampleInfo(impactSampleName)), From cb4f366651d5e1c0457296f675bfe2ed29c05f0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 19:15:55 +0900 Subject: [PATCH 236/520] Move forgotten private function down more --- .../Expanded/Accuracy/AccuracyCircle.cs | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index f62f8bfb72..eff0fa442d 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -265,34 +265,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } } - private string applauseSampleName - { - get - { - switch (score.Rank) - { - default: - case ScoreRank.D: - return @"Results/rank-applause-d"; - - case ScoreRank.C: - return @"Results/rank-applause-c"; - - case ScoreRank.B: - return @"Results/rank-applause-b"; - - case ScoreRank.A: - return @"Results/rank-applause-a"; - - case ScoreRank.S: - case ScoreRank.SH: - case ScoreRank.X: - case ScoreRank.XH: - return @"Results/rank-applause-s"; - } - } - } - protected override void Update() { base.Update(); @@ -393,6 +365,34 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } } + private string applauseSampleName + { + get + { + switch (score.Rank) + { + default: + case ScoreRank.D: + return @"Results/rank-applause-d"; + + case ScoreRank.C: + return @"Results/rank-applause-c"; + + case ScoreRank.B: + return @"Results/rank-applause-b"; + + case ScoreRank.A: + return @"Results/rank-applause-a"; + + case ScoreRank.S: + case ScoreRank.SH: + case ScoreRank.X: + case ScoreRank.XH: + return @"Results/rank-applause-s"; + } + } + } + private string impactSampleName { get From 57bc34f224a11b56b7105d8791b379da47481c61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 19:24:30 +0900 Subject: [PATCH 237/520] Move `const`s closer to usage --- .../Expanded/Accuracy/AccuracyCircle.cs | 58 +++++++------------ 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index eff0fa442d..de488f2eb9 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -76,33 +76,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy ///
public static readonly Easing ACCURACY_TRANSFORM_EASING = Easing.OutPow10; - #region Sound Effect Playback Parameters - - // swoosh-up - private const double sfx_swoosh_pre_delay = 443f; - private const double sfx_swoosh_volume = 0.4f; - - // score ticks - private const double sfx_score_tick_debounce_rate_start = 18f; - private const double sfx_score_tick_debounce_rate_end = 300f; - private const Easing sfx_score_tick_debounce_rate_easing = Easing.OutSine; - private const double sfx_score_tick_volume_start = 0.6f; - private const double sfx_score_tick_volume_end = 1.0f; - private const Easing sfx_score_tick_volume_easing = Easing.OutSine; - private const Easing sfx_score_tick_pitch_easing = Easing.OutSine; - - // badge dinks - private const double sfx_badge_dink_volume = 1f; - - // impact - private const double sfx_rank_impact_volume = 1.0f; - - // applause - private const double sfx_applause_pre_delay = 545f; - private const double sfx_applause_volume = 0.8f; - - #endregion - private readonly ScoreInfo score; private SmoothCircularProgress accuracyCircle; @@ -117,7 +90,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private PoolableSkinnableSample rankImpactSound; private PoolableSkinnableSample rankApplauseSound; - private readonly Bindable tickPlaybackRate = new Bindable(sfx_score_tick_debounce_rate_start); + private readonly Bindable tickPlaybackRate = new Bindable(); private double lastTickPlaybackTime; private bool isTicking; @@ -287,9 +260,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (withFlair) { - this.Delay(sfx_swoosh_pre_delay).Schedule(() => + const double swoosh_pre_delay = 443f; + const double swoosh_volume = 0.4f; + + this.Delay(swoosh_pre_delay).Schedule(() => { - swooshUpSound.VolumeTo(sfx_swoosh_volume); + swooshUpSound.VolumeTo(swoosh_volume); swooshUpSound.Play(); }); } @@ -307,9 +283,16 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { Schedule(() => { - scoreTickSound.FrequencyTo(1 + targetAccuracy, ACCURACY_TRANSFORM_DURATION, sfx_score_tick_pitch_easing); - scoreTickSound.VolumeTo(sfx_score_tick_volume_start).Then().VolumeTo(sfx_score_tick_volume_end, ACCURACY_TRANSFORM_DURATION, sfx_score_tick_volume_easing); - this.TransformBindableTo(tickPlaybackRate, sfx_score_tick_debounce_rate_end, ACCURACY_TRANSFORM_DURATION, sfx_score_tick_debounce_rate_easing); + const double score_tick_debounce_rate_start = 18f; + const double score_tick_debounce_rate_end = 300f; + const double score_tick_volume_start = 0.6f; + const double score_tick_volume_end = 1.0f; + + this.TransformBindableTo(tickPlaybackRate, score_tick_debounce_rate_start); + this.TransformBindableTo(tickPlaybackRate, score_tick_debounce_rate_end, ACCURACY_TRANSFORM_DURATION, Easing.OutSine); + + scoreTickSound.FrequencyTo(1 + targetAccuracy, ACCURACY_TRANSFORM_DURATION, Easing.OutSine); + scoreTickSound.VolumeTo(score_tick_volume_start).Then().VolumeTo(score_tick_volume_end, ACCURACY_TRANSFORM_DURATION, Easing.OutSine); isTicking = true; }); @@ -333,7 +316,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy var dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound; dink.FrequencyTo(1 + badgeNum++ * 0.05); - dink.VolumeTo(sfx_badge_dink_volume); dink.Play(); }); } @@ -349,15 +331,17 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Schedule(() => { isTicking = false; - rankImpactSound.VolumeTo(sfx_rank_impact_volume); rankImpactSound.Play(); }); - using (BeginDelayedSequence(sfx_applause_pre_delay)) + const double applause_pre_delay = 545f; + const double applause_volume = 0.8f; + + using (BeginDelayedSequence(applause_pre_delay)) { Schedule(() => { - rankApplauseSound.VolumeTo(sfx_applause_volume); + rankApplauseSound.VolumeTo(applause_volume); rankApplauseSound.Play(); }); } From c8947daee3865ef7a86bb0fae9f4794870669a0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 19:27:43 +0900 Subject: [PATCH 238/520] Add back another missing space... --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 6b9aaeafa9..42d197d106 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 7d86dafd4f0ee0f76c4f8753f57ca4e4488cdb12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 19:31:53 +0900 Subject: [PATCH 239/520] Simplify tick calculation/playback method --- .../Expanded/Accuracy/AccuracyCircle.cs | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index de488f2eb9..8ec76b2b18 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -238,20 +238,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } } - protected override void Update() - { - base.Update(); - - if (!isTicking) return; - - bool enoughTimePassedSinceLastPlayback = Clock.CurrentTime - lastTickPlaybackTime >= tickPlaybackRate.Value; - - if (!enoughTimePassedSinceLastPlayback) return; - - Schedule(() => scoreTickSound?.Play()); - lastTickPlaybackTime = Clock.CurrentTime; - } - protected override void LoadComplete() { base.LoadComplete(); @@ -349,6 +335,17 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } } + protected override void Update() + { + base.Update(); + + if (isTicking && Clock.CurrentTime - lastTickPlaybackTime >= tickPlaybackRate.Value) + { + scoreTickSound?.Play(); + lastTickPlaybackTime = Clock.CurrentTime; + } + } + private string applauseSampleName { get From 31b46afa7139477b9acfb73de002734ff647d431 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 19:35:05 +0900 Subject: [PATCH 240/520] Fix wrong naming scheme for applause samples --- .../Ranking/Expanded/Accuracy/AccuracyCircle.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 8ec76b2b18..87c8f95bfa 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -354,22 +354,22 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { default: case ScoreRank.D: - return @"Results/rank-applause-d"; + return @"Results/applause-d"; case ScoreRank.C: - return @"Results/rank-applause-c"; + return @"Results/applause-c"; case ScoreRank.B: - return @"Results/rank-applause-b"; + return @"Results/applause-b"; case ScoreRank.A: - return @"Results/rank-applause-a"; + return @"Results/applause-a"; case ScoreRank.S: case ScoreRank.SH: case ScoreRank.X: case ScoreRank.XH: - return @"Results/rank-applause-s"; + return @"Results/applause-s"; } } } From 209d217024bdd1d37ab63e47f6d9a3dafb6e90b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Jun 2021 12:37:56 +0900 Subject: [PATCH 241/520] Remove unused using statement --- osu.Game/Screens/Ranking/ResultsScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 9ec6b9c5b1..9f61b081f5 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Screens; -using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; From 4f8000a5744703d8235df2054ac4da7ed4d8a531 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Jun 2021 17:29:36 +0900 Subject: [PATCH 242/520] Combine cases which return the same value --- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 87c8f95bfa..73e5334739 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -385,14 +385,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy return @"Results/rank-impact-fail-d"; case ScoreRank.C: - return @"Results/rank-impact-fail"; - case ScoreRank.B: return @"Results/rank-impact-fail"; case ScoreRank.A: - return @"Results/rank-impact-pass"; - case ScoreRank.S: case ScoreRank.SH: return @"Results/rank-impact-pass"; From 0667354fbd7e98c0727effb8b9e8b6c11905f245 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Jun 2021 17:30:04 +0900 Subject: [PATCH 243/520] Remove unused resolved `skin` --- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 73e5334739..635be60549 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } [BackgroundDependencyLoader] - private void load(GameHost host, ISkinSource skin) + private void load(GameHost host) { InternalChildren = new Drawable[] { From 6a40ef581ccd8903fd2a02aa8d15b2fc6ebdca37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Jun 2021 17:39:36 +0900 Subject: [PATCH 244/520] Fix avatars missing in private message channel tabs / local leaderboards Regressed in https://github.com/ppy/osu/pull/12204. Adding this back in a central location for now, as each of the remaining cases will need a local solution. --- osu.Game/Users/Drawables/DrawableAvatar.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs index ef074813a5..87860bd149 100644 --- a/osu.Game/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -31,7 +31,9 @@ namespace osu.Game.Users.Drawables private void load(LargeTextureStore textures) { if (user != null && user.Id > 1) - Texture = textures.Get(user.AvatarUrl); + // TODO: The fallback here should not need to exist. Users should be looked up and populated via UserLookupCache or otherwise + // in remaining cases where this is required (chat tabs, local leaderboard), at which point this should be removed. + Texture = textures.Get(user.AvatarUrl ?? $@"https://a.ppy.sh/{user.Id}"); Texture ??= textures.Get(@"Online/avatar-guest"); } From 5a2e71009512bb1b9d01c870942003d47e947fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 10 Jun 2021 13:55:34 +0200 Subject: [PATCH 245/520] Split common method for metadata textbox creation --- .../Screens/Edit/Setup/MetadataSection.cs | 40 ++++++------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index c79888b9d1..3b2ac0d187 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -24,40 +24,24 @@ namespace osu.Game.Screens.Edit.Setup { Children = new Drawable[] { - artistTextBox = new LabelledTextBox - { - Label = "Artist", - FixedLabelWidth = LABEL_WIDTH, - Current = { Value = Beatmap.Metadata.Artist }, - TabbableContentContainer = this - }, - titleTextBox = new LabelledTextBox - { - Label = "Title", - FixedLabelWidth = LABEL_WIDTH, - Current = { Value = Beatmap.Metadata.Title }, - TabbableContentContainer = this - }, - creatorTextBox = new LabelledTextBox - { - Label = "Creator", - FixedLabelWidth = LABEL_WIDTH, - Current = { Value = Beatmap.Metadata.AuthorString }, - TabbableContentContainer = this - }, - difficultyTextBox = new LabelledTextBox - { - Label = "Difficulty Name", - FixedLabelWidth = LABEL_WIDTH, - Current = { Value = Beatmap.BeatmapInfo.Version }, - TabbableContentContainer = this - }, + artistTextBox = createTextBox("Artist", Beatmap.Metadata.Artist), + titleTextBox = createTextBox("Title", Beatmap.Metadata.Title), + creatorTextBox = createTextBox("Creator", Beatmap.Metadata.AuthorString), + difficultyTextBox = createTextBox("Difficulty Name", Beatmap.BeatmapInfo.Version) }; foreach (var item in Children.OfType()) item.OnCommit += onCommit; } + private LabelledTextBox createTextBox(string label, string initialValue) => new LabelledTextBox + { + Label = label, + FixedLabelWidth = LABEL_WIDTH, + Current = { Value = initialValue }, + TabbableContentContainer = this + }; + protected override void LoadComplete() { base.LoadComplete(); From 252fe0a6ccf1d2412e358563569d1a518cd92be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 10 Jun 2021 14:02:17 +0200 Subject: [PATCH 246/520] Add source and tags text boxes to metadata section --- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 14 +++++++++++--- osu.Game/Screens/Edit/Setup/SetupSection.cs | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 3b2ac0d187..6d14f6a66f 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; @@ -14,20 +13,26 @@ namespace osu.Game.Screens.Edit.Setup { private LabelledTextBox artistTextBox; private LabelledTextBox titleTextBox; + private LabelledTextBox creatorTextBox; private LabelledTextBox difficultyTextBox; + private LabelledTextBox sourceTextBox; + private LabelledTextBox tagsTextBox; public override LocalisableString Title => "Metadata"; [BackgroundDependencyLoader] private void load() { - Children = new Drawable[] + Children = new[] { artistTextBox = createTextBox("Artist", Beatmap.Metadata.Artist), titleTextBox = createTextBox("Title", Beatmap.Metadata.Title), + Empty(), creatorTextBox = createTextBox("Creator", Beatmap.Metadata.AuthorString), - difficultyTextBox = createTextBox("Difficulty Name", Beatmap.BeatmapInfo.Version) + difficultyTextBox = createTextBox("Difficulty Name", Beatmap.BeatmapInfo.Version), + sourceTextBox = createTextBox("Source", Beatmap.Metadata.Source), + tagsTextBox = createTextBox("Tags", Beatmap.Metadata.Tags) }; foreach (var item in Children.OfType()) @@ -58,8 +63,11 @@ namespace osu.Game.Screens.Edit.Setup // after switching database engines we can reconsider if switching to bindables is a good direction. Beatmap.Metadata.Artist = artistTextBox.Current.Value; Beatmap.Metadata.Title = titleTextBox.Current.Value; + Beatmap.Metadata.AuthorString = creatorTextBox.Current.Value; Beatmap.BeatmapInfo.Version = difficultyTextBox.Current.Value; + Beatmap.Metadata.Source = sourceTextBox.Current.Value; + Beatmap.Metadata.Tags = tagsTextBox.Current.Value; } } } diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs index 560e6fff67..1f988d62e2 100644 --- a/osu.Game/Screens/Edit/Setup/SetupSection.cs +++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Edit.Setup { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(20), + Spacing = new Vector2(10), Direction = FillDirection.Vertical, } } From f65f074131b2d2eb545e2614dc6b1f4ccab56fe3 Mon Sep 17 00:00:00 2001 From: ilsubyeega-desu <37479424+ilsubyeega@users.noreply.github.com> Date: Fri, 11 Jun 2021 02:46:29 +0900 Subject: [PATCH 247/520] Add star keyword to FilterQueryParser criteria --- osu.Game/Screens/Select/FilterQueryParser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index ea7f233bea..db2803d29a 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -37,6 +37,7 @@ namespace osu.Game.Screens.Select { switch (key) { + case "star": case "stars": return TryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0.01d / 2); From e41a5a0fcd2694e575027c69f8bb32c03188d6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 10 Jun 2021 15:38:56 +0200 Subject: [PATCH 248/520] Add romanised author & title fields --- osu.Game/Beatmaps/MetadataUtils.cs | 47 +++++++++++ .../UserInterfaceV2/LabelledTextBox.cs | 15 ++-- .../Edit/Setup/LabelledRomanisedTextBox.cs | 20 +++++ .../Screens/Edit/Setup/MetadataSection.cs | 78 +++++++++++++++---- 4 files changed, 136 insertions(+), 24 deletions(-) create mode 100644 osu.Game/Beatmaps/MetadataUtils.cs create mode 100644 osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs diff --git a/osu.Game/Beatmaps/MetadataUtils.cs b/osu.Game/Beatmaps/MetadataUtils.cs new file mode 100644 index 0000000000..106de43493 --- /dev/null +++ b/osu.Game/Beatmaps/MetadataUtils.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Linq; +using System.Text; + +namespace osu.Game.Beatmaps +{ + /// + /// Groups utility methods used to handle beatmap metadata. + /// + public static class MetadataUtils + { + /// + /// Returns if the character can be used in and fields. + /// Characters not matched by this method can be placed in and . + /// + public static bool IsRomanised(char c) => c <= 0xFF; + + /// + /// Returns if the string can be used in and fields. + /// Strings not matched by this method can be placed in and . + /// + public static bool IsRomanised(string? str) => str == null || str.All(IsRomanised); + + /// + /// Returns a copy of with all characters that do not match removed. + /// + public static string StripNonRomanisedCharacters(string? str) + { + if (string.IsNullOrEmpty(str)) + return string.Empty; + + var stringBuilder = new StringBuilder(str.Length); + + foreach (var c in str) + { + if (IsRomanised(c)) + stringBuilder.Append(c); + } + + return stringBuilder.ToString().Trim(); + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 266eb11319..cf2249685d 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -45,14 +45,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Component.BorderColour = colours.Blue; } - protected virtual OsuTextBox CreateTextBox() => new OsuTextBox - { - CommitOnFocusLost = true, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - CornerRadius = CORNER_RADIUS, - }; + protected virtual OsuTextBox CreateTextBox() => new OsuTextBox(); public override bool AcceptsFocus => true; @@ -64,6 +57,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override OsuTextBox CreateComponent() => CreateTextBox().With(t => { + t.CommitOnFocusLost = true; + t.Anchor = Anchor.Centre; + t.Origin = Anchor.Centre; + t.RelativeSizeAxes = Axes.X; + t.CornerRadius = CORNER_RADIUS; + t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText); }); } diff --git a/osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs b/osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs new file mode 100644 index 0000000000..ee9d86029e --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs @@ -0,0 +1,20 @@ +// 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.Beatmaps; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Screens.Edit.Setup +{ + internal class LabelledRomanisedTextBox : LabelledTextBox + { + protected override OsuTextBox CreateTextBox() => new RomanisedTextBox(); + + private class RomanisedTextBox : OsuTextBox + { + protected override bool CanAddCharacter(char character) + => MetadataUtils.IsRomanised(character); + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 6d14f6a66f..c543242957 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -5,6 +5,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Screens.Edit.Setup @@ -12,7 +13,10 @@ namespace osu.Game.Screens.Edit.Setup internal class MetadataSection : SetupSection { private LabelledTextBox artistTextBox; + private LabelledTextBox romanisedArtistTextBox; + private LabelledTextBox titleTextBox; + private LabelledTextBox romanisedTitleTextBox; private LabelledTextBox creatorTextBox; private LabelledTextBox difficultyTextBox; @@ -24,28 +28,43 @@ namespace osu.Game.Screens.Edit.Setup [BackgroundDependencyLoader] private void load() { + var metadata = Beatmap.Metadata; + Children = new[] { - artistTextBox = createTextBox("Artist", Beatmap.Metadata.Artist), - titleTextBox = createTextBox("Title", Beatmap.Metadata.Title), + artistTextBox = createTextBox("Artist", + !string.IsNullOrEmpty(metadata.ArtistUnicode) ? metadata.ArtistUnicode : metadata.Artist), + romanisedArtistTextBox = createTextBox("Romanised Artist", + !string.IsNullOrEmpty(metadata.Artist) ? metadata.Artist : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode)), + Empty(), - creatorTextBox = createTextBox("Creator", Beatmap.Metadata.AuthorString), - difficultyTextBox = createTextBox("Difficulty Name", Beatmap.BeatmapInfo.Version), - sourceTextBox = createTextBox("Source", Beatmap.Metadata.Source), - tagsTextBox = createTextBox("Tags", Beatmap.Metadata.Tags) + + titleTextBox = createTextBox("Title", + !string.IsNullOrEmpty(metadata.TitleUnicode) ? metadata.TitleUnicode : metadata.Title), + romanisedTitleTextBox = createTextBox("Romanised Title", + !string.IsNullOrEmpty(metadata.Title) ? metadata.Title : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode)), + + Empty(), + + creatorTextBox = createTextBox("Creator", metadata.AuthorString), + difficultyTextBox = createTextBox("Difficulty Name", Beatmap.BeatmapInfo.Version), + sourceTextBox = createTextBox("Source", metadata.Source), + tagsTextBox = createTextBox("Tags", metadata.Tags) }; foreach (var item in Children.OfType()) item.OnCommit += onCommit; } - private LabelledTextBox createTextBox(string label, string initialValue) => new LabelledTextBox - { - Label = label, - FixedLabelWidth = LABEL_WIDTH, - Current = { Value = initialValue }, - TabbableContentContainer = this - }; + private TTextBox createTextBox(string label, string initialValue) + where TTextBox : LabelledTextBox, new() + => new TTextBox + { + Label = label, + FixedLabelWidth = LABEL_WIDTH, + Current = { Value = initialValue }, + TabbableContentContainer = this + }; protected override void LoadComplete() { @@ -53,16 +72,43 @@ namespace osu.Game.Screens.Edit.Setup if (string.IsNullOrEmpty(artistTextBox.Current.Value)) GetContainingInputManager().ChangeFocus(artistTextBox); + + artistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, romanisedArtistTextBox)); + titleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, romanisedTitleTextBox)); + updateReadOnlyState(); + } + + private void transferIfRomanised(string value, LabelledTextBox target) + { + if (MetadataUtils.IsRomanised(value)) + target.Current.Value = value; + + updateReadOnlyState(); + updateMetadata(); + } + + private void updateReadOnlyState() + { + romanisedArtistTextBox.ReadOnly = MetadataUtils.IsRomanised(artistTextBox.Current.Value); + romanisedTitleTextBox.ReadOnly = MetadataUtils.IsRomanised(titleTextBox.Current.Value); } private void onCommit(TextBox sender, bool newText) { if (!newText) return; - // for now, update these on commit rather than making BeatmapMetadata bindables. + // for now, update on commit rather than making BeatmapMetadata bindables. // after switching database engines we can reconsider if switching to bindables is a good direction. - Beatmap.Metadata.Artist = artistTextBox.Current.Value; - Beatmap.Metadata.Title = titleTextBox.Current.Value; + updateMetadata(); + } + + private void updateMetadata() + { + Beatmap.Metadata.ArtistUnicode = artistTextBox.Current.Value; + Beatmap.Metadata.Artist = romanisedArtistTextBox.Current.Value; + + Beatmap.Metadata.TitleUnicode = titleTextBox.Current.Value; + Beatmap.Metadata.Title = romanisedTitleTextBox.Current.Value; Beatmap.Metadata.AuthorString = creatorTextBox.Current.Value; Beatmap.BeatmapInfo.Version = difficultyTextBox.Current.Value; From 417aaacc533375855e74b6af788d0f6b5112c74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 10 Jun 2021 20:34:52 +0200 Subject: [PATCH 249/520] Add test coverage for romanised data transfer --- .../Editing/TestSceneMetadataSection.cs | 144 ++++++++++++++++++ .../UserInterfaceV2/LabelledTextBox.cs | 1 + .../Screens/Edit/Setup/MetadataSection.cs | 36 ++--- 3 files changed, 163 insertions(+), 18 deletions(-) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs new file mode 100644 index 0000000000..19081f3281 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Setup; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneMetadataSection : OsuTestScene + { + [Cached] + private EditorBeatmap editorBeatmap = new EditorBeatmap(new Beatmap()); + + private TestMetadataSection metadataSection; + + [Test] + public void TestMinimalMetadata() + { + AddStep("set metadata", () => + { + editorBeatmap.Metadata.Artist = "Example Artist"; + editorBeatmap.Metadata.ArtistUnicode = null; + + editorBeatmap.Metadata.Title = "Example Title"; + editorBeatmap.Metadata.TitleUnicode = null; + }); + + createSection(); + + assertArtist("Example Artist"); + assertRomanisedArtist("Example Artist", false); + + assertTitle("Example Title"); + assertRomanisedTitle("Example Title", false); + } + + [Test] + public void TestInitialisationFromNonRomanisedVariant() + { + AddStep("set metadata", () => + { + editorBeatmap.Metadata.ArtistUnicode = "*なみりん"; + editorBeatmap.Metadata.Artist = null; + + editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット"; + editorBeatmap.Metadata.Title = null; + }); + + createSection(); + + assertArtist("*なみりん"); + assertRomanisedArtist(string.Empty, true); + + assertTitle("コイシテイク・プラネット"); + assertRomanisedTitle(string.Empty, true); + } + + [Test] + public void TestInitialisationPreservesOriginalValues() + { + AddStep("set metadata", () => + { + editorBeatmap.Metadata.ArtistUnicode = "*なみりん"; + editorBeatmap.Metadata.Artist = "*namirin"; + + editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット"; + editorBeatmap.Metadata.Title = "Koishiteiku Planet"; + }); + + createSection(); + + assertArtist("*なみりん"); + assertRomanisedArtist("*namirin", true); + + assertTitle("コイシテイク・プラネット"); + assertRomanisedTitle("Koishiteiku Planet", true); + } + + [Test] + public void TestValueTransfer() + { + AddStep("set metadata", () => + { + editorBeatmap.Metadata.ArtistUnicode = "*なみりん"; + editorBeatmap.Metadata.Artist = null; + + editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット"; + editorBeatmap.Metadata.Title = null; + }); + + createSection(); + + AddStep("set romanised artist name", () => metadataSection.ArtistTextBox.Current.Value = "*namirin"); + assertArtist("*namirin"); + assertRomanisedArtist("*namirin", false); + + AddStep("set native artist name", () => metadataSection.ArtistTextBox.Current.Value = "*なみりん"); + assertArtist("*なみりん"); + assertRomanisedArtist("*namirin", true); + + AddStep("set romanised title", () => metadataSection.TitleTextBox.Current.Value = "Hitokoto no kyori"); + assertTitle("Hitokoto no kyori"); + assertRomanisedTitle("Hitokoto no kyori", false); + + AddStep("set native title", () => metadataSection.TitleTextBox.Current.Value = "ヒトコトの距離"); + assertTitle("ヒトコトの距離"); + assertRomanisedTitle("Hitokoto no kyori", true); + } + + private void createSection() + => AddStep("create metadata section", () => Child = metadataSection = new TestMetadataSection()); + + private void assertArtist(string expected) + => AddAssert($"artist is {expected}", () => metadataSection.ArtistTextBox.Current.Value == expected); + + private void assertRomanisedArtist(string expected, bool editable) + { + AddAssert($"romanised artist is {expected}", () => metadataSection.RomanisedArtistTextBox.Current.Value == expected); + AddAssert($"romanised artist is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedArtistTextBox.ReadOnly == !editable); + } + + private void assertTitle(string expected) + => AddAssert($"title is {expected}", () => metadataSection.TitleTextBox.Current.Value == expected); + + private void assertRomanisedTitle(string expected, bool editable) + { + AddAssert($"romanised title is {expected}", () => metadataSection.RomanisedTitleTextBox.Current.Value == expected); + AddAssert($"romanised title is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedTitleTextBox.ReadOnly == !editable); + } + + private class TestMetadataSection : MetadataSection + { + public new LabelledTextBox ArtistTextBox => base.ArtistTextBox; + public new LabelledTextBox RomanisedArtistTextBox => base.RomanisedArtistTextBox; + + public new LabelledTextBox TitleTextBox => base.TitleTextBox; + public new LabelledTextBox RomanisedTitleTextBox => base.RomanisedTitleTextBox; + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index cf2249685d..4da8d6a554 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -21,6 +21,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 public bool ReadOnly { + get => Component.ReadOnly; set => Component.ReadOnly = value; } diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index c543242957..9e93b0b038 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -12,11 +12,11 @@ namespace osu.Game.Screens.Edit.Setup { internal class MetadataSection : SetupSection { - private LabelledTextBox artistTextBox; - private LabelledTextBox romanisedArtistTextBox; + protected LabelledTextBox ArtistTextBox; + protected LabelledTextBox RomanisedArtistTextBox; - private LabelledTextBox titleTextBox; - private LabelledTextBox romanisedTitleTextBox; + protected LabelledTextBox TitleTextBox; + protected LabelledTextBox RomanisedTitleTextBox; private LabelledTextBox creatorTextBox; private LabelledTextBox difficultyTextBox; @@ -32,16 +32,16 @@ namespace osu.Game.Screens.Edit.Setup Children = new[] { - artistTextBox = createTextBox("Artist", + ArtistTextBox = createTextBox("Artist", !string.IsNullOrEmpty(metadata.ArtistUnicode) ? metadata.ArtistUnicode : metadata.Artist), - romanisedArtistTextBox = createTextBox("Romanised Artist", + RomanisedArtistTextBox = createTextBox("Romanised Artist", !string.IsNullOrEmpty(metadata.Artist) ? metadata.Artist : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode)), Empty(), - titleTextBox = createTextBox("Title", + TitleTextBox = createTextBox("Title", !string.IsNullOrEmpty(metadata.TitleUnicode) ? metadata.TitleUnicode : metadata.Title), - romanisedTitleTextBox = createTextBox("Romanised Title", + RomanisedTitleTextBox = createTextBox("Romanised Title", !string.IsNullOrEmpty(metadata.Title) ? metadata.Title : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode)), Empty(), @@ -70,11 +70,11 @@ namespace osu.Game.Screens.Edit.Setup { base.LoadComplete(); - if (string.IsNullOrEmpty(artistTextBox.Current.Value)) - GetContainingInputManager().ChangeFocus(artistTextBox); + if (string.IsNullOrEmpty(ArtistTextBox.Current.Value)) + GetContainingInputManager().ChangeFocus(ArtistTextBox); - artistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, romanisedArtistTextBox)); - titleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, romanisedTitleTextBox)); + ArtistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, RomanisedArtistTextBox)); + TitleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, RomanisedTitleTextBox)); updateReadOnlyState(); } @@ -89,8 +89,8 @@ namespace osu.Game.Screens.Edit.Setup private void updateReadOnlyState() { - romanisedArtistTextBox.ReadOnly = MetadataUtils.IsRomanised(artistTextBox.Current.Value); - romanisedTitleTextBox.ReadOnly = MetadataUtils.IsRomanised(titleTextBox.Current.Value); + RomanisedArtistTextBox.ReadOnly = MetadataUtils.IsRomanised(ArtistTextBox.Current.Value); + RomanisedTitleTextBox.ReadOnly = MetadataUtils.IsRomanised(TitleTextBox.Current.Value); } private void onCommit(TextBox sender, bool newText) @@ -104,11 +104,11 @@ namespace osu.Game.Screens.Edit.Setup private void updateMetadata() { - Beatmap.Metadata.ArtistUnicode = artistTextBox.Current.Value; - Beatmap.Metadata.Artist = romanisedArtistTextBox.Current.Value; + Beatmap.Metadata.ArtistUnicode = ArtistTextBox.Current.Value; + Beatmap.Metadata.Artist = RomanisedArtistTextBox.Current.Value; - Beatmap.Metadata.TitleUnicode = titleTextBox.Current.Value; - Beatmap.Metadata.Title = romanisedTitleTextBox.Current.Value; + Beatmap.Metadata.TitleUnicode = TitleTextBox.Current.Value; + Beatmap.Metadata.Title = RomanisedTitleTextBox.Current.Value; Beatmap.Metadata.AuthorString = creatorTextBox.Current.Value; Beatmap.BeatmapInfo.Version = difficultyTextBox.Current.Value; From 24c249b17eda95ce51255057753bdcda949131d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 10 Jun 2021 22:40:49 +0200 Subject: [PATCH 250/520] Add test coverage --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 49389e67aa..9bd262a569 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -33,10 +33,11 @@ namespace osu.Game.Tests.NonVisual.Filtering * outside of the range. */ - [Test] - public void TestApplyStarQueries() + [TestCase("star")] + [TestCase("stars")] + public void TestApplyStarQueries(string variant) { - const string query = "stars<4 easy"; + string query = $"{variant}<4 easy"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("easy", filterCriteria.SearchText.Trim()); From 38bf04d7ff15df0575d9831baefb608f837d66a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 13:25:09 +0900 Subject: [PATCH 251/520] Give more space for time values to allow for negative offsets --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index fe63138d28..7a98cf63c3 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private EditorClock clock { get; set; } - public const float TIMING_COLUMN_WIDTH = 220; + public const float TIMING_COLUMN_WIDTH = 230; public IEnumerable ControlGroups { @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Edit.Timing { Text = group.Time.ToEditorFormattedString(), Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold), - Width = 60, + Width = 70, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, From 375f64ffd1b44fff175b736ad2d4ff4e30419af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Jun 2021 06:38:53 +0200 Subject: [PATCH 252/520] Check empty string more explicitly in `IsRomanised()` Co-authored-by: Dan Balasescu --- osu.Game/Beatmaps/MetadataUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/MetadataUtils.cs b/osu.Game/Beatmaps/MetadataUtils.cs index 106de43493..56f5e3fe35 100644 --- a/osu.Game/Beatmaps/MetadataUtils.cs +++ b/osu.Game/Beatmaps/MetadataUtils.cs @@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps /// Returns if the string can be used in and fields. /// Strings not matched by this method can be placed in and . ///
- public static bool IsRomanised(string? str) => str == null || str.All(IsRomanised); + public static bool IsRomanised(string? str) => string.IsNullOrEmpty(str) || str.All(IsRomanised); /// /// Returns a copy of with all characters that do not match removed. From bc3b7233ab3ea19389f5ce449b95631a3f74ca3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 14:17:29 +0900 Subject: [PATCH 253/520] Show osu!taiko centre/rim colouring in editor timeline Closes #13443. --- osu.Game.Rulesets.Taiko/Objects/Hit.cs | 17 +++++- .../Objects/Types/IHasAccentColour.cs | 19 +++++++ .../Timeline/TimelineHitObjectBlueprint.cs | 53 +++++++++++++------ 3 files changed, 71 insertions(+), 18 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasAccentColour.cs diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index b4ed242893..bbee54d139 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -3,14 +3,19 @@ using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Game.Audio; +using osu.Game.Rulesets.Objects.Types; +using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.Objects { - public class Hit : TaikoStrongableHitObject + public class Hit : TaikoStrongableHitObject, IHasDisplayColour { public readonly Bindable TypeBindable = new Bindable(); + public Bindable DisplayColour { get; } = new Bindable(colour_centre); + /// /// The that actuates this . /// @@ -20,9 +25,17 @@ namespace osu.Game.Rulesets.Taiko.Objects set => TypeBindable.Value = value; } + private static readonly Color4 colour_centre = Color4Extensions.FromHex(@"bb1177"); + private static readonly Color4 colour_rim = Color4Extensions.FromHex(@"2299bb"); + public Hit() { - TypeBindable.BindValueChanged(_ => updateSamplesFromType()); + TypeBindable.BindValueChanged(_ => + { + updateSamplesFromType(); + DisplayColour.Value = Type == HitType.Centre ? colour_centre : colour_rim; + }); + SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples()); } diff --git a/osu.Game/Rulesets/Objects/Types/IHasAccentColour.cs b/osu.Game/Rulesets/Objects/Types/IHasAccentColour.cs new file mode 100644 index 0000000000..8807b802d8 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasAccentColour.cs @@ -0,0 +1,19 @@ +// 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.Bindables; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject which has a preferred display colour. Will be used for editor timeline display. + /// + public interface IHasDisplayColour + { + /// + /// The current display colour of this hit object. + /// + Bindable DisplayColour { get; } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index dbe689be2f..377c37c4c7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -39,6 +39,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable indexInCurrentComboBindable; private Bindable comboIndexBindable; + private Bindable displayColourBindable; private readonly ExtendableCircle circle; private readonly Border border; @@ -108,44 +109,64 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadComplete(); - if (Item is IHasComboInformation comboInfo) + switch (Item) { - indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy(); - indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true); + case IHasDisplayColour displayColour: + displayColourBindable = displayColour.DisplayColour.GetBoundCopy(); + displayColourBindable.BindValueChanged(_ => updateColour(), true); + break; - comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy(); - comboIndexBindable.BindValueChanged(_ => updateComboColour(), true); + case IHasComboInformation comboInfo: + indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy(); + indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true); - skin.SourceChanged += updateComboColour; + comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy(); + comboIndexBindable.BindValueChanged(_ => updateColour(), true); + + skin.SourceChanged += updateColour; + break; } } protected override void OnSelected() { // base logic hides selected blueprints when not selected, but timeline doesn't do that. - updateComboColour(); + updateColour(); } protected override void OnDeselected() { // base logic hides selected blueprints when not selected, but timeline doesn't do that. - updateComboColour(); + updateColour(); } private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString(); - private void updateComboColour() + private void updateColour() { - if (!(Item is IHasComboInformation combo)) - return; + Color4 colour; - var comboColours = skin.GetConfig>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty(); - var comboColour = combo.GetComboColour(comboColours); + switch (Item) + { + case IHasDisplayColour displayColour: + colour = displayColour.DisplayColour.Value; + break; + + case IHasComboInformation combo: + { + var comboColours = skin.GetConfig>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty(); + colour = combo.GetComboColour(comboColours); + break; + } + + default: + return; + } if (IsSelected) { border.Show(); - comboColour = comboColour.Lighten(0.3f); + colour = colour.Lighten(0.3f); } else { @@ -153,9 +174,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } if (Item is IHasDuration duration && duration.Duration > 0) - circle.Colour = ColourInfo.GradientHorizontal(comboColour, comboColour.Lighten(0.4f)); + circle.Colour = ColourInfo.GradientHorizontal(colour, colour.Lighten(0.4f)); else - circle.Colour = comboColour; + circle.Colour = colour; var col = circle.Colour.TopLeft.Linear; colouredComponents.Colour = OsuColour.ForegroundTextColourFor(col); From 9c34cb07778b19c843172ead6fdc1cef62c1a617 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 14:20:08 +0900 Subject: [PATCH 254/520] Share colour constants with default drawable piece implementations --- osu.Game.Rulesets.Taiko/Objects/Hit.cs | 8 ++++---- .../Skinning/Default/CentreHitCirclePiece.cs | 3 ++- .../Skinning/Default/RimHitCirclePiece.cs | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index bbee54d139..2038da9344 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Objects { public readonly Bindable TypeBindable = new Bindable(); - public Bindable DisplayColour { get; } = new Bindable(colour_centre); + public Bindable DisplayColour { get; } = new Bindable(COLOUR_CENTRE); /// /// The that actuates this . @@ -25,15 +25,15 @@ namespace osu.Game.Rulesets.Taiko.Objects set => TypeBindable.Value = value; } - private static readonly Color4 colour_centre = Color4Extensions.FromHex(@"bb1177"); - private static readonly Color4 colour_rim = Color4Extensions.FromHex(@"2299bb"); + public static readonly Color4 COLOUR_CENTRE = Color4Extensions.FromHex(@"bb1177"); + public static readonly Color4 COLOUR_RIM = Color4Extensions.FromHex(@"2299bb"); public Hit() { TypeBindable.BindValueChanged(_ => { updateSamplesFromType(); - DisplayColour.Value = Type == HitType.Centre ? colour_centre : colour_rim; + DisplayColour.Value = Type == HitType.Centre ? COLOUR_CENTRE : COLOUR_RIM; }); SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples()); diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs index f65bb54726..455b2fc596 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Rulesets.Taiko.Objects; using osuTK; namespace osu.Game.Rulesets.Taiko.Skinning.Default @@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default [BackgroundDependencyLoader] private void load(OsuColour colours) { - AccentColour = colours.PinkDarker; + AccentColour = Hit.COLOUR_CENTRE; } /// diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs index ca2ab301be..bd21d511b1 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Rulesets.Taiko.Objects; using osuTK; using osuTK.Graphics; @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default [BackgroundDependencyLoader] private void load(OsuColour colours) { - AccentColour = colours.BlueDarker; + AccentColour = Hit.COLOUR_RIM; } /// From 562cfe8703bdafb09bbc3292975a8fef7817d969 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 14:34:18 +0900 Subject: [PATCH 255/520] Fix filename not matching type rename --- .../Objects/Types/{IHasAccentColour.cs => IHasDisplayColour.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game/Rulesets/Objects/Types/{IHasAccentColour.cs => IHasDisplayColour.cs} (100%) diff --git a/osu.Game/Rulesets/Objects/Types/IHasAccentColour.cs b/osu.Game/Rulesets/Objects/Types/IHasDisplayColour.cs similarity index 100% rename from osu.Game/Rulesets/Objects/Types/IHasAccentColour.cs rename to osu.Game/Rulesets/Objects/Types/IHasDisplayColour.cs From b9050f91a440c8d10cd3637d0fd179c38298a9c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 14:49:35 +0900 Subject: [PATCH 256/520] Expose as `Skin`s and consume `SkinInfo` from instances --- osu.Game/Skinning/SkinManager.cs | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 89f166dc2a..ffdbadf54c 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -48,19 +48,15 @@ namespace osu.Game.Skinning protected override string ImportFromStablePath => "Skins"; - private readonly Skin defaultSkin; - /// /// An providing the resources of the default skin. /// - public ISkin DefaultSkin => defaultSkin; - - private readonly Skin defaultLegacySkin; + public Skin DefaultSkin { get; } /// /// An providing the resources of the default legacy skin. /// - public ISkin DefaultLegacySkin => defaultLegacySkin; + public Skin DefaultLegacySkin { get; } public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, IResourceStore resources, AudioManager audio) : base(storage, contextFactory, new SkinStore(contextFactory, storage), host) @@ -69,12 +65,12 @@ namespace osu.Game.Skinning this.host = host; this.resources = resources; - defaultLegacySkin = new DefaultLegacySkin(this); - defaultSkin = new DefaultSkin(this); + DefaultLegacySkin = new DefaultLegacySkin(this); + DefaultSkin = new DefaultSkin(this); CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue); - CurrentSkin.Value = defaultSkin; + CurrentSkin.Value = DefaultSkin; CurrentSkin.ValueChanged += skin => { if (skin.NewValue.SkinInfo != CurrentSkinInfo.Value) @@ -93,8 +89,8 @@ namespace osu.Game.Skinning public List GetAllUsableSkins() { var userSkins = GetAllUserSkins(); - userSkins.Insert(0, SkinInfo.Default); - userSkins.Insert(1, Skinning.DefaultLegacySkin.Info); + userSkins.Insert(0, DefaultSkin.SkinInfo); + userSkins.Insert(1, DefaultLegacySkin.SkinInfo); return userSkins; } @@ -236,11 +232,11 @@ namespace osu.Game.Skinning if (lookupFunction(CurrentSkin.Value)) return CurrentSkin.Value; - if (CurrentSkin.Value is LegacySkin && lookupFunction(defaultLegacySkin)) - return defaultLegacySkin; + if (CurrentSkin.Value is LegacySkin && lookupFunction(DefaultLegacySkin)) + return DefaultLegacySkin; - if (lookupFunction(defaultSkin)) - return defaultSkin; + if (lookupFunction(DefaultSkin)) + return DefaultSkin; return null; } @@ -254,11 +250,11 @@ namespace osu.Game.Skinning // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. // When attempting to address this, we may want to move the full DefaultLegacySkin fallback logic to within Player itself (to better allow // for beatmap skin visibility). - if (CurrentSkin.Value is LegacySkin && lookupFunction(defaultLegacySkin) is T legacySourced) + if (CurrentSkin.Value is LegacySkin && lookupFunction(DefaultLegacySkin) is T legacySourced) return legacySourced; // Finally fall back to the (non-legacy) default. - return lookupFunction(defaultSkin); + return lookupFunction(DefaultSkin); } #region IResourceStorageProvider From debd359d2e5d36361757d37188b4498c530bdee0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 14:50:21 +0900 Subject: [PATCH 257/520] Update xmldoc --- osu.Game/Skinning/SkinManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index ffdbadf54c..660f44772c 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -49,12 +49,12 @@ namespace osu.Game.Skinning protected override string ImportFromStablePath => "Skins"; /// - /// An providing the resources of the default skin. + /// The default skin. /// public Skin DefaultSkin { get; } /// - /// An providing the resources of the default legacy skin. + /// The default legacy skin. /// public Skin DefaultLegacySkin { get; } From 8d0840020b4048839bcfd5f7676cd79ef24bd1be Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 11 Jun 2021 15:33:13 +0900 Subject: [PATCH 258/520] Specify legacy skin version of old-skin testing skin Old-style catcher sprite is not supported for all versions --- osu.Game.Rulesets.Catch.Tests/Resources/old-skin/skin.ini | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/old-skin/skin.ini diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/skin.ini b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/skin.ini new file mode 100644 index 0000000000..1596c95912 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/skin.ini @@ -0,0 +1,2 @@ +[General] +Version: 1.0 From 7f7c2c73e002177f7518ca876e75b735024ebaea Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 11 Jun 2021 15:39:06 +0900 Subject: [PATCH 259/520] Move catcher movement logic of `Catcher` to `CatcherArea` --- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 6 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 76 ++++-------------- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 78 +++++++++++++++++-- osu.Game.Rulesets.Catch/UI/Direction.cs | 11 +++ 4 files changed, 99 insertions(+), 72 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/UI/Direction.cs diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 1e42c6a240..73b60f51a4 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -33,13 +33,13 @@ namespace osu.Game.Rulesets.Catch.Mods private class MouseInputHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition { - private readonly Catcher catcher; + private readonly CatcherArea catcherArea; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; public MouseInputHelper(CatchPlayfield playfield) { - catcher = playfield.CatcherArea.MovableCatcher; + catcherArea = playfield.CatcherArea; RelativeSizeAxes = Axes.Both; } @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Mods protected override bool OnMouseMove(MouseMoveEvent e) { - catcher.UpdatePosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH); + catcherArea.SetCatcherPosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH); return base.OnMouseMove(e); } } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 4af2243ed4..ee2986c73c 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Bindings; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -26,7 +25,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.UI { - public class Catcher : SkinReloadableDrawable, IKeyBindingHandler + public class Catcher : SkinReloadableDrawable { /// /// The default colour used to tint hyper-dash fruit, along with the moving catcher, its trail @@ -54,6 +53,11 @@ namespace osu.Game.Rulesets.Catch.UI /// public const double BASE_SPEED = 1.0; + /// + /// The current speed of the catcher. + /// + public double Speed => (Dashing ? 1 : 0.5) * BASE_SPEED * hyperDashModifier; + /// /// The amount by which caught fruit should be offset from the plate surface to make them look visually "caught". /// @@ -96,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.UI public bool Dashing { get => dashing; - protected set + set { if (value == dashing) return; @@ -106,6 +110,12 @@ namespace osu.Game.Rulesets.Catch.UI } } + public Direction VisualDirection + { + get => Scale.X > 0 ? Direction.Right : Direction.Left; + set => Scale = new Vector2((value == Direction.Right ? 1 : -1) * Math.Abs(Scale.X), Scale.Y); + } + /// /// Width of the area that can be used to attempt catches during gameplay. /// @@ -116,8 +126,6 @@ namespace osu.Game.Rulesets.Catch.UI private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR; private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR; - private int currentDirection; - private double hyperDashModifier = 1; private int hyperDashDirection; private float hyperDashTargetPosition; @@ -315,55 +323,6 @@ namespace osu.Game.Rulesets.Catch.UI } } - public void UpdatePosition(float position) - { - position = Math.Clamp(position, 0, CatchPlayfield.WIDTH); - - if (position == X) - return; - - Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y); - X = position; - } - - public bool OnPressed(CatchAction action) - { - switch (action) - { - case CatchAction.MoveLeft: - currentDirection--; - return true; - - case CatchAction.MoveRight: - currentDirection++; - return true; - - case CatchAction.Dash: - Dashing = true; - return true; - } - - return false; - } - - public void OnReleased(CatchAction action) - { - switch (action) - { - case CatchAction.MoveLeft: - currentDirection++; - break; - - case CatchAction.MoveRight: - currentDirection--; - break; - - case CatchAction.Dash: - Dashing = false; - break; - } - } - /// /// Drop any fruit off the plate. /// @@ -405,15 +364,6 @@ namespace osu.Game.Rulesets.Catch.UI { base.Update(); - if (currentDirection == 0) return; - - var direction = Math.Sign(currentDirection); - - var dashModifier = Dashing ? 1 : 0.5; - var speed = BASE_SPEED * dashModifier * hyperDashModifier; - - UpdatePosition((float)(X + direction * Clock.ElapsedFrameTime * speed)); - // Correct overshooting. if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) || (hyperDashDirection < 0 && hyperDashTargetPosition > X)) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 44adbd5512..cdb15c2b4c 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -1,8 +1,10 @@ // 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.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects.Drawables; @@ -14,13 +16,20 @@ using osuTK; namespace osu.Game.Rulesets.Catch.UI { - public class CatcherArea : Container + public class CatcherArea : Container, IKeyBindingHandler { public const float CATCHER_SIZE = 106.75f; public readonly Catcher MovableCatcher; private readonly CatchComboDisplay comboDisplay; + /// + /// -1 when only left button is pressed. + /// 1 when only right button is pressed. + /// 0 when none or both left and right buttons are pressed. + /// + private int currentDirection; + public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null) { Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); @@ -63,16 +72,73 @@ namespace osu.Game.Rulesets.Catch.UI MovableCatcher.OnRevertResult(hitObject, result); } + protected override void Update() + { + base.Update(); + + var replayState = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState; + + SetCatcherPosition( + replayState?.CatcherX ?? + (float)(MovableCatcher.X + MovableCatcher.Speed * currentDirection * Clock.ElapsedFrameTime)); + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - var state = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState; - - if (state?.CatcherX != null) - MovableCatcher.X = state.CatcherX.Value; - comboDisplay.X = MovableCatcher.X; } + + public void SetCatcherPosition(float X) + { + float lastPosition = MovableCatcher.X; + float newPosition = Math.Clamp(X, 0, CatchPlayfield.WIDTH); + + MovableCatcher.X = newPosition; + + if (lastPosition < newPosition) + MovableCatcher.VisualDirection = Direction.Right; + else if (lastPosition > newPosition) + MovableCatcher.VisualDirection = Direction.Left; + } + + public bool OnPressed(CatchAction action) + { + switch (action) + { + case CatchAction.MoveLeft: + currentDirection--; + return true; + + case CatchAction.MoveRight: + currentDirection++; + return true; + + case CatchAction.Dash: + MovableCatcher.Dashing = true; + return true; + } + + return false; + } + + public void OnReleased(CatchAction action) + { + switch (action) + { + case CatchAction.MoveLeft: + currentDirection++; + break; + + case CatchAction.MoveRight: + currentDirection--; + break; + + case CatchAction.Dash: + MovableCatcher.Dashing = false; + break; + } + } } } diff --git a/osu.Game.Rulesets.Catch/UI/Direction.cs b/osu.Game.Rulesets.Catch/UI/Direction.cs new file mode 100644 index 0000000000..65f064b7fb --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/Direction.cs @@ -0,0 +1,11 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Catch.UI +{ + public enum Direction + { + Right = 1, + Left = -1 + } +} From 33aec57238bd02076635d3894d0e1dc5f6bd4747 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 15:45:34 +0900 Subject: [PATCH 260/520] Replace 1.0 version in old skin test assets with none --- osu.Game.Rulesets.Catch.Tests/Resources/old-skin/skin.ini | 2 +- osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini | 4 ++-- osu.Game.Tests/Resources/old-skin/skin.ini | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/skin.ini b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/skin.ini index 1596c95912..94c6b5b58d 100644 --- a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/skin.ini +++ b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/skin.ini @@ -1,2 +1,2 @@ [General] -Version: 1.0 +// no version specified means v1 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini index 89bcd68343..06dfa6b7be 100644 --- a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini +++ b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini @@ -1,6 +1,6 @@ [General] -Version: 1.0 +// no version specified means v1 [Fonts] HitCircleOverlap: 3 -ScoreOverlap: 3 \ No newline at end of file +ScoreOverlap: 3 diff --git a/osu.Game.Tests/Resources/old-skin/skin.ini b/osu.Game.Tests/Resources/old-skin/skin.ini index 5369de24e9..94c6b5b58d 100644 --- a/osu.Game.Tests/Resources/old-skin/skin.ini +++ b/osu.Game.Tests/Resources/old-skin/skin.ini @@ -1,2 +1,2 @@ [General] -Version: 1.0 \ No newline at end of file +// no version specified means v1 From 46b379899e0d7299ff8ebb37d3de83701414b9aa Mon Sep 17 00:00:00 2001 From: Ivan Pavluk Date: Fri, 11 Jun 2021 14:07:38 +0700 Subject: [PATCH 261/520] add taiko hd mod (2nd attempt) --- .../Mods/TaikoModHidden.cs | 76 ++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 7739ecaf5b..eeebe66b77 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -1,23 +1,95 @@ // 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.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModHidden : ModHidden + public class TaikoModHidden : ModHidden, IApplicableToDifficulty { public override string Description => @"Beats fade out before you hit them!"; public override double ScoreMultiplier => 1.06; - public override bool HasImplementation => false; + + // In stable Taiko, hit position is 160, so playfield is essentially 160 pixels shorter + // than actual screen width. Normalized screen height is 480, so on a 4:3 screen the + // playfield ratio will actually be (640 - 160) / 480 = 1 + // For 16:9 resolutions, screen width with normalized height becomes 853.33333 instead, + // meaning 16:9 playfield ratio is (853.333 - 160) / 480 = 1.444444. + // Thus, 4:3 ratio / 16:9 ratio = 1 / 1.4444 = 9 / 13 + private const double hd_sv_scale = 9.0 / 13.0; + private BeatmapDifficulty difficulty; + private ControlPointInfo controlPointInfo; + + [SettingSource("Hide Time", "Multiplies the time the note stays hidden")] + public BindableNumber VisibilityMod { get; } = new BindableDouble + { + MinValue = 0.5, + // Max visibility is only to be used with max playfield size + MaxValue = 1.5, + Default = 1.0, + Value = 1.0, + Precision = 0.01, + }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { + ApplyNormalVisibilityState(hitObject, state); + } + + protected double MultiplierAt(double position) + { + var beatLength = controlPointInfo.TimingPointAt(position)?.BeatLength; + var speedMultiplier = controlPointInfo.DifficultyPointAt(position)?.SpeedMultiplier; + return difficulty.SliderMultiplier * (speedMultiplier ?? 1.0) * TimingControlPoint.DEFAULT_BEAT_LENGTH / (beatLength ?? TimingControlPoint.DEFAULT_BEAT_LENGTH); } protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { + switch (hitObject) + { + case DrawableDrumRollTick _: + break; + + case DrawableHit _: + break; + + default: + return; + } + + // I *think* it's like this because stable's default velocity multiplier is 1.4 + var preempt = 14000 / MultiplierAt(hitObject.HitObject.StartTime) * VisibilityMod.Value; + var start = hitObject.HitObject.StartTime - preempt * 0.6; + var duration = preempt * 0.3; + + using (hitObject.BeginAbsoluteSequence(start)) + { + // With 0 opacity the object is dying, and if I set a lifetime other issues appear... + // Ideally these need to be fixed, but I lack the knowledge to do it, and this is good enough anyway. + hitObject.FadeTo(0.0005f, duration); + } + } + + public void ReadFromDifficulty(BeatmapDifficulty difficulty) + { + } + + public void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + this.difficulty = difficulty; + difficulty.SliderMultiplier /= hd_sv_scale; + } + + public override void ApplyToBeatmap(IBeatmap beatmap) + { + controlPointInfo = beatmap.ControlPointInfo; } } } From c00f9ae4b750d905a7165d3d7bd8b69782f95433 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 16:11:37 +0900 Subject: [PATCH 262/520] Reword settings text --- .../Settings/Sections/Online/AlertsAndPrivacySettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs index f9f5b927b7..9f70d23c27 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs @@ -18,12 +18,12 @@ namespace osu.Game.Overlays.Settings.Sections.Online { new SettingsCheckbox { - LabelText = "Show a notification popup when someone says your name", + LabelText = "Show a notification when someone mentions your name", Current = config.GetBindable(OsuSetting.ChatHighlightName) }, new SettingsCheckbox { - LabelText = "Show private message notifications", + LabelText = "Show a notification when you receive a private message", Current = config.GetBindable(OsuSetting.ChatMessageNotification) }, }; From f00967388a5296f5d6a1dc193bf324ba279466a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 16:17:42 +0900 Subject: [PATCH 263/520] Refactor tests a bit --- .../Visual/Online/TestSceneMessageNotifier.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 28ec3e91a1..80c0c86fa3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -197,26 +197,34 @@ namespace osu.Game.Tests.Visual.Online private class TestContainer : Container { - private readonly Channel[] channels; - - public TestContainer(Channel[] channels) => this.channels = channels; - [Cached] public ChannelManager ChannelManager { get; } = new ChannelManager(); [Cached] public NotificationOverlay NotificationOverlay { get; } = new NotificationOverlay(); - [Cached] - public MessageNotifier MessageNotifier { get; } = new MessageNotifier(); - [Cached] public ChatOverlay ChatOverlay { get; } = new ChatOverlay(); + private readonly MessageNotifier messageNotifier = new MessageNotifier(); + + private readonly Channel[] channels; + + public TestContainer(Channel[] channels) + { + this.channels = channels; + } + [BackgroundDependencyLoader] private void load() { - AddRange(new Drawable[] { ChannelManager, ChatOverlay, NotificationOverlay, MessageNotifier }); + Children = new Drawable[] + { + ChannelManager, + ChatOverlay, + NotificationOverlay, + messageNotifier, + }; ((BindableList)ChannelManager.AvailableChannels).AddRange(channels); From 16e3a197380524a86031383e7eded8069681f40a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 16:18:51 +0900 Subject: [PATCH 264/520] Fix notification overlay not being in correct place in test scene --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 80c0c86fa3..d193856217 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -201,7 +201,11 @@ namespace osu.Game.Tests.Visual.Online public ChannelManager ChannelManager { get; } = new ChannelManager(); [Cached] - public NotificationOverlay NotificationOverlay { get; } = new NotificationOverlay(); + public NotificationOverlay NotificationOverlay { get; } = new NotificationOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }; [Cached] public ChatOverlay ChatOverlay { get; } = new ChatOverlay(); From 296761ade5ea6912fb39875ff87aea45fa327b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Jun 2021 09:18:24 +0200 Subject: [PATCH 265/520] Add missing `CurrentSkin` null check in DHO disposal --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 5fd2b2493e..7fc35fc778 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -716,7 +716,8 @@ namespace osu.Game.Rulesets.Objects.Drawables if (HitObject != null) HitObject.DefaultsApplied -= onDefaultsApplied; - CurrentSkin.SourceChanged -= skinSourceChanged; + if (CurrentSkin != null) + CurrentSkin.SourceChanged -= skinSourceChanged; } } From 139401a04a4813072362d6807271cc07e01b0e22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 16:27:31 +0900 Subject: [PATCH 266/520] Inline and refactor overly verbose `MessageNotifier` code --- osu.Game/Online/Chat/MessageNotifier.cs | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 53bd3c61c3..685545f08c 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -45,8 +45,7 @@ namespace osu.Game.Online.Chat notifyOnPM = config.GetBindable(OsuSetting.ChatMessageNotification); localUser.BindTo(api.LocalUser); - // Listen for new messages - joinedChannels.CollectionChanged += channelsChanged; + joinedChannels.BindCollectionChanged(channelsChanged); joinedChannels.BindTo(channelManager.JoinedChannels); } @@ -70,28 +69,14 @@ namespace osu.Game.Online.Chat private void newMessagesArrived(IEnumerable messages) { - if (messages == null || !messages.Any()) + if (!messages.Any()) return; - HandleMessages(messages.First().ChannelId, messages); - } - - /// - /// Searches for a channel with the matching , returns when none found. - /// - private Channel fetchJoinedChannel(long channelId) - { - return channelManager.JoinedChannels.SingleOrDefault(c => c.Id == channelId); - } - - public void HandleMessages(long channelId, IEnumerable messages) - { - // Fetch channel object - var channel = fetchJoinedChannel(channelId); + var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == messages.First().ChannelId); if (channel == null) { - Logger.Log($"Couldn't resolve channel id {channelId}", LoggingTarget.Information); + Logger.Log($"Couldn't resolve channel id {messages.First().ChannelId}", LoggingTarget.Information); return; } From 3d645608eb9b46a94c5f3f453c929686b160dfc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 16:28:53 +0900 Subject: [PATCH 267/520] Remove nullability of DI dependencies and fix incorrect load order --- osu.Game/Online/Chat/MessageNotifier.cs | 18 +++++++----------- osu.Game/OsuGame.cs | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 685545f08c..2a676738d0 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -24,13 +24,13 @@ namespace osu.Game.Online.Chat /// public class MessageNotifier : Component { - [Resolved(CanBeNull = true)] - private NotificationOverlay notificationOverlay { get; set; } + [Resolved] + private NotificationOverlay notifications { get; set; } - [Resolved(CanBeNull = true)] + [Resolved] private ChatOverlay chatOverlay { get; set; } - [Resolved(CanBeNull = true)] + [Resolved] private ChannelManager channelManager { get; set; } private Bindable notifyOnMention; @@ -81,7 +81,7 @@ namespace osu.Game.Online.Chat } // Only send notifications, if ChatOverlay and the target channel aren't visible. - if (chatOverlay?.IsPresent == true && channelManager.CurrentChannel.Value == channel) + if (chatOverlay.IsPresent && channelManager.CurrentChannel.Value == channel) return; foreach (var message in messages.OrderByDescending(m => m.Id)) @@ -115,9 +115,7 @@ namespace osu.Game.Online.Chat if (channel.Id != message.ChannelId) throw new ArgumentException("The provided channel doesn't match with the channel id provided by the message parameter.", nameof(channel)); - var notification = new PrivateMessageNotification(message.Sender.Username, channel); - notificationOverlay?.Post(notification); - + notifications.Post(new PrivateMessageNotification(message.Sender.Username, channel)); return true; } @@ -135,9 +133,7 @@ namespace osu.Game.Online.Chat if (channel.Id != message.ChannelId) throw new ArgumentException("The provided channel doesn't match with the channel id provided by the message parameter.", nameof(channel)); - var notification = new MentionNotification(message.Sender.Username, channel); - notificationOverlay?.Post(notification); - + notifications.Post(new MentionNotification(message.Sender.Username, channel)); return true; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e753dd1424..0cd31def2e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -726,8 +726,8 @@ namespace osu.Game loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true); var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true); loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true); - loadComponentSingleFile(new MessageNotifier(), AddInternal, true); loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true); + loadComponentSingleFile(new MessageNotifier(), AddInternal, true); loadComponentSingleFile(Settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true); var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true); loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true); From 20759657def9491b25d49f12d65a6b81d9c8e6c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 16:37:31 +0900 Subject: [PATCH 268/520] Rename configuration variables and refactor lots more --- osu.Game/Configuration/OsuConfigManager.cs | 8 +- osu.Game/Online/Chat/MessageNotifier.cs | 96 ++++++++----------- .../Online/AlertsAndPrivacySettings.cs | 4 +- 3 files changed, 46 insertions(+), 62 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 1c92c16333..60a0d5a0ac 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -61,8 +61,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ShowOnlineExplicitContent, false); - SetDefault(OsuSetting.ChatHighlightName, true); - SetDefault(OsuSetting.ChatMessageNotification, true); + SetDefault(OsuSetting.NotifyOnUsernameMentioned, true); + SetDefault(OsuSetting.NotifyOnPrivateMessage, true); // Audio SetDefault(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); @@ -262,8 +262,8 @@ namespace osu.Game.Configuration ScalingSizeY, UIScale, IntroSequence, - ChatHighlightName, - ChatMessageNotification, + NotifyOnUsernameMentioned, + NotifyOnPrivateMessage, UIHoldActivationDelay, HitLighting, MenuBackgroundSource, diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 2a676738d0..b8947d6e47 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Online.API; @@ -33,16 +32,18 @@ namespace osu.Game.Online.Chat [Resolved] private ChannelManager channelManager { get; set; } - private Bindable notifyOnMention; - private Bindable notifyOnPM; + private Bindable notifyOnUsername; + private Bindable notifyOnPrivateMessage; + private readonly IBindable localUser = new Bindable(); private readonly IBindableList joinedChannels = new BindableList(); [BackgroundDependencyLoader] private void load(OsuConfigManager config, IAPIProvider api) { - notifyOnMention = config.GetBindable(OsuSetting.ChatHighlightName); - notifyOnPM = config.GetBindable(OsuSetting.ChatMessageNotification); + notifyOnUsername = config.GetBindable(OsuSetting.NotifyOnUsernameMentioned); + notifyOnPrivateMessage = config.GetBindable(OsuSetting.NotifyOnPrivateMessage); + localUser.BindTo(api.LocalUser); joinedChannels.BindCollectionChanged(channelsChanged); @@ -55,19 +56,19 @@ namespace osu.Game.Online.Chat { case NotifyCollectionChangedAction.Add: foreach (var channel in e.NewItems.Cast()) - channel.NewMessagesArrived += newMessagesArrived; + channel.NewMessagesArrived += checkNewMessages; break; case NotifyCollectionChangedAction.Remove: foreach (var channel in e.OldItems.Cast()) - channel.NewMessagesArrived -= newMessagesArrived; + channel.NewMessagesArrived -= checkNewMessages; break; } } - private void newMessagesArrived(IEnumerable messages) + private void checkNewMessages(IEnumerable messages) { if (!messages.Any()) return; @@ -75,10 +76,7 @@ namespace osu.Game.Online.Chat var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == messages.First().ChannelId); if (channel == null) - { - Logger.Log($"Couldn't resolve channel id {messages.First().ChannelId}", LoggingTarget.Information); return; - } // Only send notifications, if ChatOverlay and the target channel aren't visible. if (chatOverlay.IsPresent && channelManager.CurrentChannel.Value == channel) @@ -93,12 +91,11 @@ namespace osu.Game.Online.Chat if (message.Sender.Id == localUser.Value.Id) continue; - // check for private messages first, - // to avoid both posting two notifications about the same message + // check for private messages first to avoid both posting two notifications about the same message if (checkForPMs(channel, message)) continue; - _ = checkForMentions(channel, message, localUser.Value.Username); + checkForMentions(channel, message); } } @@ -107,45 +104,52 @@ namespace osu.Game.Online.Chat /// /// The channel associated to the /// The message to be checked + /// Whether a notification was fired. private bool checkForPMs(Channel channel, Message message) { - if (!notifyOnPM.Value || channel.Type != ChannelType.PM) + if (!notifyOnPrivateMessage.Value || channel.Type != ChannelType.PM) return false; - if (channel.Id != message.ChannelId) - throw new ArgumentException("The provided channel doesn't match with the channel id provided by the message parameter.", nameof(channel)); - notifications.Post(new PrivateMessageNotification(message.Sender.Username, channel)); return true; } - /// - /// Checks whether the user enabled mention notifications and whether specified mentions the provided . - /// - /// The channel associated to the - /// The message to be checked - /// The username that will be checked for - private bool checkForMentions(Channel channel, Message message, string username) + private void checkForMentions(Channel channel, Message message) { - if (!notifyOnMention.Value || !isMentioning(message.Content, username)) - return false; - - if (channel.Id != message.ChannelId) - throw new ArgumentException("The provided channel doesn't match with the channel id provided by the message parameter.", nameof(channel)); + if (!notifyOnUsername.Value || !checkContainsUsername(message.Content, localUser.Value.Username)) return; notifications.Post(new MentionNotification(message.Sender.Username, channel)); - return true; } /// - /// Checks if contains , if not, retries making spaces into underscores. + /// Checks if contains . + /// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces). /// - /// If the mentions the - private static bool isMentioning(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase); + private static bool checkContainsUsername(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase); - public class OpenChannelNotification : SimpleNotification + public class PrivateMessageNotification : OpenChannelNotification { - public OpenChannelNotification(Channel channel) + public PrivateMessageNotification(string username, Channel channel) + : base(channel) + { + Icon = FontAwesome.Solid.Envelope; + Text = $"You received a private message from '{username}'. Click to read it!"; + } + } + + public class MentionNotification : OpenChannelNotification + { + public MentionNotification(string username, Channel channel) + : base(channel) + { + Icon = FontAwesome.Solid.At; + Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; + } + } + + public abstract class OpenChannelNotification : SimpleNotification + { + protected OpenChannelNotification(Channel channel) { this.channel = channel; } @@ -169,25 +173,5 @@ namespace osu.Game.Online.Chat }; } } - - public class PrivateMessageNotification : OpenChannelNotification - { - public PrivateMessageNotification(string username, Channel channel) - : base(channel) - { - Icon = FontAwesome.Solid.Envelope; - Text = $"You received a private message from '{username}'. Click to read it!"; - } - } - - public class MentionNotification : OpenChannelNotification - { - public MentionNotification(string username, Channel channel) - : base(channel) - { - Icon = FontAwesome.Solid.At; - Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; - } - } } } diff --git a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs index 9f70d23c27..b0f6400d4f 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs @@ -19,12 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Online new SettingsCheckbox { LabelText = "Show a notification when someone mentions your name", - Current = config.GetBindable(OsuSetting.ChatHighlightName) + Current = config.GetBindable(OsuSetting.NotifyOnUsernameMentioned) }, new SettingsCheckbox { LabelText = "Show a notification when you receive a private message", - Current = config.GetBindable(OsuSetting.ChatMessageNotification) + Current = config.GetBindable(OsuSetting.NotifyOnPrivateMessage) }, }; } From 09df23e2a6f275893f8996f01b89682c7bbbdf9c Mon Sep 17 00:00:00 2001 From: Ivan Pavluk Date: Fri, 11 Jun 2021 15:07:41 +0700 Subject: [PATCH 269/520] improve reasoning for hd_sv_scale --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index eeebe66b77..58125d4a65 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -20,10 +20,10 @@ namespace osu.Game.Rulesets.Taiko.Mods // In stable Taiko, hit position is 160, so playfield is essentially 160 pixels shorter // than actual screen width. Normalized screen height is 480, so on a 4:3 screen the // playfield ratio will actually be (640 - 160) / 480 = 1 - // For 16:9 resolutions, screen width with normalized height becomes 853.33333 instead, - // meaning 16:9 playfield ratio is (853.333 - 160) / 480 = 1.444444. - // Thus, 4:3 ratio / 16:9 ratio = 1 / 1.4444 = 9 / 13 - private const double hd_sv_scale = 9.0 / 13.0; + // For custom resolutions (x:y), screen width with normalized height becomes 480 * x / y instead, + // and the playfield ratio becomes (480 * x / y - 160) / 480 = x / y - 1/3 + // The following is 4:3 playfield ratio divided by 16:9 playfield ratio + private const double hd_sv_scale = (4 / 3 - 1/3) / (16 / 9 - 1/3); private BeatmapDifficulty difficulty; private ControlPointInfo controlPointInfo; From e34e26ae521de2bff25a27d9362476dccdd6e3f7 Mon Sep 17 00:00:00 2001 From: Ivan Pavluk Date: Fri, 11 Jun 2021 15:12:05 +0700 Subject: [PATCH 270/520] remove outdated comment --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 58125d4a65..e04d617e3c 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Taiko.Mods public BindableNumber VisibilityMod { get; } = new BindableDouble { MinValue = 0.5, - // Max visibility is only to be used with max playfield size MaxValue = 1.5, Default = 1.0, Value = 1.0, From a985e3b8d3562e357d3f431482794999e40948b9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 11:25:07 +0300 Subject: [PATCH 271/520] Apply documentation settings for better readability Co-authored-by: Dean Herbert Co-authored-by: Dan Balasescu --- osu.Game/Skinning/SkinProvidingContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 078c666472..0f2d8e2c22 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Skinning public event Action SourceChanged; /// - /// The list of skins provided by this . + /// Skins which should be exposed by this container, in order of lookup precedence. /// protected readonly BindableList SkinSources = new BindableList(); @@ -44,7 +44,7 @@ namespace osu.Game.Skinning protected virtual bool AllowColourLookup => true; /// - /// Constructs a new with a single skin added to the protected list. + /// Constructs a new initialised with a single skin source. /// public SkinProvidingContainer(ISkin skin) : this() @@ -54,7 +54,7 @@ namespace osu.Game.Skinning /// /// Constructs a new with no sources. - /// Up to the implementation for adding to the list. + /// Implementations can add or change sources through the list. /// protected SkinProvidingContainer() { From 813285275307871b77479effcf17ce9d797c845e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 11:34:22 +0300 Subject: [PATCH 272/520] Add other affectable change action cases --- osu.Game/Skinning/SkinProvidingContainer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 0f2d8e2c22..ab33a66265 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -72,11 +72,21 @@ namespace osu.Game.Skinning break; + case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Remove: foreach (var source in args.OldItems.Cast().OfType()) source.SourceChanged -= OnSourceChanged; break; + + case NotifyCollectionChangedAction.Replace: + foreach (var source in args.OldItems.Cast().OfType()) + source.SourceChanged -= OnSourceChanged; + + foreach (var source in args.NewItems.Cast().OfType()) + source.SourceChanged += OnSourceChanged; + + break; } }), true); } From 2e01e611775c6a143d7363a51a9f03a4ef9a59d6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 11:46:29 +0300 Subject: [PATCH 273/520] Move TODO comment to correct location --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 1 + osu.Game/Skinning/SkinManager.cs | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8087043230..c57522726d 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -56,6 +56,7 @@ namespace osu.Game.Skinning SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.CurrentSkin.Value, Beatmap)); + // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. if (skinManager.CurrentSkin.Value is LegacySkin) SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, Beatmap)); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 660f44772c..7acc52809f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -247,9 +247,6 @@ namespace osu.Game.Skinning if (lookupFunction(CurrentSkin.Value) is T skinSourced) return skinSourced; - // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. - // When attempting to address this, we may want to move the full DefaultLegacySkin fallback logic to within Player itself (to better allow - // for beatmap skin visibility). if (CurrentSkin.Value is LegacySkin && lookupFunction(DefaultLegacySkin) is T legacySourced) return legacySourced; From 8eab7df9551f150c394a3df174aa228868983082 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 17:51:58 +0900 Subject: [PATCH 274/520] Move `BindCollectionChanged` out of async load --- osu.Game/Online/Chat/MessageNotifier.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index b8947d6e47..6840c036ff 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -45,11 +45,15 @@ namespace osu.Game.Online.Chat notifyOnPrivateMessage = config.GetBindable(OsuSetting.NotifyOnPrivateMessage); localUser.BindTo(api.LocalUser); - - joinedChannels.BindCollectionChanged(channelsChanged); joinedChannels.BindTo(channelManager.JoinedChannels); } + protected override void LoadComplete() + { + base.LoadComplete(); + joinedChannels.BindCollectionChanged(channelsChanged, true); + } + private void channelsChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) From 6d06066ddee137a2f5f3c930bbc7cdda6d397497 Mon Sep 17 00:00:00 2001 From: Ivan Pavluk Date: Fri, 11 Jun 2021 15:54:30 +0700 Subject: [PATCH 275/520] forgot to run code inspection --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index e04d617e3c..b837866c4d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Mods // For custom resolutions (x:y), screen width with normalized height becomes 480 * x / y instead, // and the playfield ratio becomes (480 * x / y - 160) / 480 = x / y - 1/3 // The following is 4:3 playfield ratio divided by 16:9 playfield ratio - private const double hd_sv_scale = (4 / 3 - 1/3) / (16 / 9 - 1/3); + private const double hd_sv_scale = (4.0 / 3.0 - 1.0 / 3.0) / (16.0 / 9.0 - 1.0 / 3.0); private BeatmapDifficulty difficulty; private ControlPointInfo controlPointInfo; From af3f253b2182784c99072e3df362cbb07714c97d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 11 Jun 2021 18:28:48 +0900 Subject: [PATCH 276/520] Refactor `ScrollingHitObjectContainer` and expose more useful methods --- .../Scrolling/ScrollingHitObjectContainer.cs | 148 +++++++----------- 1 file changed, 57 insertions(+), 91 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index f478e37e3e..d21f30eb30 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -19,6 +19,11 @@ namespace osu.Game.Rulesets.UI.Scrolling private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); + /// + /// 0 for horizontal scroll, 1 for vertical scroll. + /// + private int scrollingAxis => direction.Value == ScrollingDirection.Left || direction.Value == ScrollingDirection.Right ? 0 : 1; + /// /// A set of top-level s which have an up-to-date layout. /// @@ -48,85 +53,65 @@ namespace osu.Game.Rulesets.UI.Scrolling } /// - /// Given a position in screen space, return the time within this column. + /// Given a position along the scrolling axis, return the time within this . /// - public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) + /// The position along the scrolling axis. + /// The time the scrolling speed is used. + public double TimeAtPosition(float position, double referenceTime) { - // convert to local space of column so we can snap and fetch correct location. - Vector2 localPosition = ToLocalSpace(screenSpacePosition); - - float position = 0; - - switch (scrollingInfo.Direction.Value) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - position = localPosition.Y; - break; - - case ScrollingDirection.Right: - case ScrollingDirection.Left: - position = localPosition.X; - break; - } - flipPositionIfRequired(ref position); - - return scrollingInfo.Algorithm.TimeAt(position, Time.Current, scrollingInfo.TimeRange.Value, scrollLength); + return scrollingInfo.Algorithm.TimeAt(position, referenceTime, timeRange.Value, scrollLength); } /// - /// Given a time, return the screen space position within this column. + /// Given a position in screen space, return the time within this . + /// + public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) + { + Vector2 localPosition = ToLocalSpace(screenSpacePosition); + return TimeAtPosition(localPosition[scrollingAxis], Time.Current); + } + + /// + /// Given a time, return the position along the scrolling axis within this at time . + /// + public float PositionAtTime(double time, double currentTime) + { + float pos = scrollingInfo.Algorithm.PositionAt(time, currentTime, timeRange.Value, scrollLength); + flipPositionIfRequired(ref pos); + return pos; + } + + /// + /// Given a time, return the position along the scrolling axis within this at the current time. + /// + public float PositionAtTime(double time) => PositionAtTime(time, Time.Current); + + /// + /// Given a time, return the screen space position within this . + /// In the non-scrolling axis, the center of this is returned. /// public Vector2 ScreenSpacePositionAtTime(double time) { - var pos = scrollingInfo.Algorithm.PositionAt(time, Time.Current, scrollingInfo.TimeRange.Value, scrollLength); - - flipPositionIfRequired(ref pos); - - switch (scrollingInfo.Direction.Value) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - return ToScreenSpace(new Vector2(getBreadth() / 2, pos)); - - default: - return ToScreenSpace(new Vector2(pos, getBreadth() / 2)); - } + float position = PositionAtTime(time, Time.Current); + return scrollingAxis == 0 + ? ToScreenSpace(new Vector2(position, DrawHeight / 2)) + : ToScreenSpace(new Vector2(DrawWidth / 2, position)); } - private float scrollLength + /// + /// Given a start time and end time of a scrolling object, return the length of the object along the scrolling axis. + /// + public float LengthAtTime(double startTime, double endTime) { - get - { - switch (scrollingInfo.Direction.Value) - { - case ScrollingDirection.Left: - case ScrollingDirection.Right: - return DrawWidth; - - default: - return DrawHeight; - } - } + return scrollingInfo.Algorithm.GetLength(startTime, endTime, timeRange.Value, scrollLength); } - private float getBreadth() - { - switch (scrollingInfo.Direction.Value) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - return DrawWidth; - - default: - return DrawHeight; - } - } + private float scrollLength => DrawSize[scrollingAxis]; private void flipPositionIfRequired(ref float position) { - // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. + // We're dealing with coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position, // so when scrolling downwards the coordinates need to be flipped. @@ -237,18 +222,11 @@ namespace osu.Game.Rulesets.UI.Scrolling { if (hitObject.HitObject is IHasDuration e) { - switch (direction.Value) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); - break; - - case ScrollingDirection.Left: - case ScrollingDirection.Right: - hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); - break; - } + float length = LengthAtTime(hitObject.HitObject.StartTime, e.EndTime); + if (scrollingAxis == 0) + hitObject.Width = length; + else + hitObject.Height = length; } foreach (var obj in hitObject.NestedHitObjects) @@ -262,24 +240,12 @@ namespace osu.Game.Rulesets.UI.Scrolling private void updatePosition(DrawableHitObject hitObject, double currentTime) { - switch (direction.Value) - { - case ScrollingDirection.Up: - hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); - break; + float position = PositionAtTime(hitObject.HitObject.StartTime, currentTime); - case ScrollingDirection.Down: - hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); - break; - - case ScrollingDirection.Left: - hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); - break; - - case ScrollingDirection.Right: - hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); - break; - } + if (scrollingAxis == 0) + hitObject.X = position; + else + hitObject.Y = position; } } } From 9e16359f18a7e90049a1dacb80bad9ff2fe8d130 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 12:29:28 +0300 Subject: [PATCH 277/520] Refactor disallowing in `SkinProvidingContainer` to become per source Fixes `FindProvider` becoming completely broken, because of no way to perform the checks on one skin source. --- osu.Game/Skinning/SkinProvidingContainer.cs | 188 ++++++++++---------- 1 file changed, 96 insertions(+), 92 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index ab33a66265..c9bb3a6ec4 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using JetBrains.Annotations; @@ -28,11 +29,15 @@ namespace osu.Game.Skinning /// protected readonly BindableList SkinSources = new BindableList(); + /// + /// A dictionary mapping each from the + /// to one that performs the "allow lookup" checks before proceeding with a lookup. + /// + private readonly Dictionary disableableSkinSources = new Dictionary(); + [CanBeNull] private ISkinSource fallbackSource; - private readonly NoFallbackProxy noFallbackLookupProxy; - protected virtual bool AllowDrawableLookup(ISkinComponent component) => true; protected virtual bool AllowTextureLookup(string componentName) => true; @@ -60,31 +65,49 @@ namespace osu.Game.Skinning { RelativeSizeAxes = Axes.Both; - noFallbackLookupProxy = new NoFallbackProxy(this); - SkinSources.BindCollectionChanged(((_, args) => { switch (args.Action) { case NotifyCollectionChangedAction.Add: - foreach (var source in args.NewItems.Cast().OfType()) - source.SourceChanged += OnSourceChanged; + foreach (var skin in args.NewItems.Cast()) + { + disableableSkinSources.Add(skin, new DisableableSkinSource(skin, this)); + + if (skin is ISkinSource source) + source.SourceChanged += OnSourceChanged; + } break; case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Remove: - foreach (var source in args.OldItems.Cast().OfType()) - source.SourceChanged -= OnSourceChanged; + foreach (var skin in args.OldItems.Cast()) + { + disableableSkinSources.Remove(skin); + + if (skin is ISkinSource source) + source.SourceChanged -= OnSourceChanged; + } break; case NotifyCollectionChangedAction.Replace: - foreach (var source in args.OldItems.Cast().OfType()) - source.SourceChanged -= OnSourceChanged; + foreach (var skin in args.OldItems.Cast()) + { + disableableSkinSources.Remove(skin); - foreach (var source in args.NewItems.Cast().OfType()) - source.SourceChanged += OnSourceChanged; + if (skin is ISkinSource source) + source.SourceChanged -= OnSourceChanged; + } + + foreach (var skin in args.NewItems.Cast()) + { + disableableSkinSources.Add(skin, new DisableableSkinSource(skin, this)); + + if (skin is ISkinSource source) + source.SourceChanged += OnSourceChanged; + } break; } @@ -95,8 +118,7 @@ namespace osu.Game.Skinning { foreach (var skin in SkinSources) { - // a proxy must be used here to correctly pass through the "Allow" checks without implicitly falling back to the fallbackSource. - if (lookupFunction(noFallbackLookupProxy)) + if (lookupFunction(disableableSkinSources[skin])) return skin; } @@ -104,94 +126,50 @@ namespace osu.Game.Skinning } public Drawable GetDrawableComponent(ISkinComponent component) - => GetDrawableComponent(component, true); - - public Drawable GetDrawableComponent(ISkinComponent component, bool fallback) { - if (AllowDrawableLookup(component)) + foreach (var skin in SkinSources) { - foreach (var skin in SkinSources) - { - Drawable sourceDrawable; - if ((sourceDrawable = skin?.GetDrawableComponent(component)) != null) - return sourceDrawable; - } + Drawable sourceDrawable; + if ((sourceDrawable = disableableSkinSources[skin]?.GetDrawableComponent(component)) != null) + return sourceDrawable; } - if (!fallback) - return null; - return fallbackSource?.GetDrawableComponent(component); } public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) - => GetTexture(componentName, wrapModeS, wrapModeT, true); - - public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT, bool fallback) { - if (AllowTextureLookup(componentName)) + foreach (var skin in SkinSources) { - foreach (var skin in SkinSources) - { - Texture sourceTexture; - if ((sourceTexture = skin?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) - return sourceTexture; - } + Texture sourceTexture; + if ((sourceTexture = disableableSkinSources[skin]?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) + return sourceTexture; } - if (!fallback) - return null; - return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT); } public ISample GetSample(ISampleInfo sampleInfo) - => GetSample(sampleInfo, true); - - public ISample GetSample(ISampleInfo sampleInfo, bool fallback) { - if (AllowSampleLookup(sampleInfo)) + foreach (var skin in SkinSources) { - foreach (var skin in SkinSources) - { - ISample sourceSample; - if ((sourceSample = skin?.GetSample(sampleInfo)) != null) - return sourceSample; - } + ISample sourceSample; + if ((sourceSample = disableableSkinSources[skin]?.GetSample(sampleInfo)) != null) + return sourceSample; } - if (!fallback) - return null; - return fallbackSource?.GetSample(sampleInfo); } public IBindable GetConfig(TLookup lookup) - => GetConfig(lookup, true); - - public IBindable GetConfig(TLookup lookup, bool fallback) { - if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) - return lookupWithFallback(lookup, AllowColourLookup, fallback); - - return lookupWithFallback(lookup, AllowConfigurationLookup, fallback); - } - - private IBindable lookupWithFallback(TLookup lookup, bool canUseSkinLookup, bool canUseFallback) - { - if (canUseSkinLookup) + foreach (var skin in SkinSources) { - foreach (var skin in SkinSources) - { - IBindable bindable; - if ((bindable = skin?.GetConfig(lookup)) != null) - return bindable; - } + IBindable bindable; + if ((bindable = disableableSkinSources[skin]?.GetConfig(lookup)) != null) + return bindable; } - if (!canUseFallback) - return null; - return fallbackSource?.GetConfig(lookup); } @@ -224,35 +202,61 @@ namespace osu.Game.Skinning source.SourceChanged -= OnSourceChanged; } - private class NoFallbackProxy : ISkinSource + private class DisableableSkinSource : ISkin { + private readonly ISkin skin; private readonly SkinProvidingContainer provider; - public NoFallbackProxy(SkinProvidingContainer provider) + public DisableableSkinSource(ISkin skin, SkinProvidingContainer provider) { + this.skin = skin; this.provider = provider; } public Drawable GetDrawableComponent(ISkinComponent component) - => provider.GetDrawableComponent(component, false); - - public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) - => provider.GetTexture(componentName, wrapModeS, wrapModeT, false); - - public ISample GetSample(ISampleInfo sampleInfo) - => provider.GetSample(sampleInfo, false); - - public IBindable GetConfig(TLookup lookup) - => provider.GetConfig(lookup, false); - - public event Action SourceChanged { - add => provider.SourceChanged += value; - remove => provider.SourceChanged -= value; + if (provider.AllowDrawableLookup(component)) + return skin.GetDrawableComponent(component); + + return null; } - public ISkin FindProvider(Func lookupFunction) => - provider.FindProvider(lookupFunction); + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) + { + if (provider.AllowTextureLookup(componentName)) + return skin.GetTexture(componentName, wrapModeS, wrapModeT); + + return null; + } + + public ISample GetSample(ISampleInfo sampleInfo) + { + if (provider.AllowSampleLookup(sampleInfo)) + return skin.GetSample(sampleInfo); + + return null; + } + + public IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case GlobalSkinColours _: + case SkinCustomColourLookup _: + if (provider.AllowColourLookup) + return skin.GetConfig(lookup); + + break; + + default: + if (provider.AllowConfigurationLookup) + return skin.GetConfig(lookup); + + break; + } + + return null; + } } } } From e59beffc4e7057254c4790a3b9112c5e6c09f3ff Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 12:44:25 +0300 Subject: [PATCH 278/520] Forward all base transformer lookup methods to `Skin` --- osu.Game/Skinning/LegacySkinTransformer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index fedd63c7de..92b7a04dee 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -30,7 +30,7 @@ namespace osu.Game.Skinning Skin = skin ?? throw new ArgumentNullException(nameof(skin)); } - public abstract Drawable GetDrawableComponent(ISkinComponent component); + public virtual Drawable GetDrawableComponent(ISkinComponent component) => Skin.GetDrawableComponent(component); public Texture GetTexture(string componentName) => GetTexture(componentName, default, default); @@ -49,6 +49,6 @@ namespace osu.Game.Skinning return Skin.GetSample(sampleInfo); } - public abstract IBindable GetConfig(TLookup lookup); + public virtual IBindable GetConfig(TLookup lookup) => Skin.GetConfig(lookup); } } From fbb856d84bfdfb2e5c4c5ee3fbfde90302864000 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 12:44:44 +0300 Subject: [PATCH 279/520] Call `base` when overriding lookup methods Rather than arbitrarily accessing `Skin` here and there. --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 8 ++++---- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 12 ++++++------ .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 12 ++++++------ .../Skinning/Legacy/TaikoLegacySkinTransformer.cs | 12 +++--------- 4 files changed, 19 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index a5a1d1504f..287ed1b4c7 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (targetComponent.Target) { case SkinnableTarget.MainHUDComponents: - var components = Skin.GetDrawableComponent(component) as SkinnableTargetComponentsContainer; + var components = base.GetDrawableComponent(component) as SkinnableTargetComponentsContainer; if (providesComboCounter && components != null) { @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy } } - return Skin.GetDrawableComponent(component); + return base.GetDrawableComponent(component); } public override IBindable GetConfig(TLookup lookup) @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (lookup) { case CatchSkinColour colour: - var result = (Bindable)Skin.GetConfig(new SkinCustomColourLookup(colour)); + var result = (Bindable)base.GetConfig(new SkinCustomColourLookup(colour)); if (result == null) return null; @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy return (IBindable)result; } - return Skin.GetConfig(lookup); + return base.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 7d4d303bc9..814a737034 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -63,11 +63,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { this.beatmap = (ManiaBeatmap)beatmap; - isLegacySkin = new Lazy(() => skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); + isLegacySkin = new Lazy(() => GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => { var keyImage = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value ?? "mania-key1"; - return skin.GetAnimation(keyImage, true, true) != null; + return this.GetAnimation(keyImage, true, true) != null; }); } @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy break; } - return Skin.GetDrawableComponent(component); + return base.GetDrawableComponent(component); } private Drawable getResult(HitResult result) @@ -142,15 +142,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered) return new SampleVirtual(); - return Skin.GetSample(sampleInfo); + return base.GetSample(sampleInfo); } public override IBindable GetConfig(TLookup lookup) { if (lookup is ManiaSkinConfigurationLookup maniaLookup) - return Skin.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn)); + return base.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn)); - return Skin.GetConfig(lookup); + return base.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 3ad3b7d30b..41b0a88f11 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy public OsuLegacySkinTransformer(ISkin skin) : base(skin) { - hasHitCircle = new Lazy(() => Skin.GetTexture("hitcircle") != null); + hasHitCircle = new Lazy(() => GetTexture("hitcircle") != null); } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } } - return Skin.GetDrawableComponent(component); + return base.GetDrawableComponent(component); } public override IBindable GetConfig(TLookup lookup) @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy switch (lookup) { case OsuSkinColour colour: - return Skin.GetConfig(new SkinCustomColourLookup(colour)); + return base.GetConfig(new SkinCustomColourLookup(colour)); case OsuSkinConfiguration osuLookup: switch (osuLookup) @@ -133,14 +133,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy case OsuSkinConfiguration.HitCircleOverlayAboveNumber: // See https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D // HitCircleOverlayAboveNumer (with typo) should still be supported for now. - return Skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? - Skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); + return base.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? + base.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; } - return Skin.GetConfig(lookup); + return base.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index 0122f9a1cd..a3ecbbc436 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Audio; using osu.Game.Rulesets.Scoring; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public TaikoLegacySkinTransformer(ISkin skin) : base(skin) { - hasExplosion = new Lazy(() => Skin.GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null); + hasExplosion = new Lazy(() => GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null); } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -50,7 +49,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy case TaikoSkinComponents.CentreHit: case TaikoSkinComponents.RimHit: - if (GetTexture("taikohitcircle") != null) return new LegacyHit(taikoComponent.Component); @@ -85,7 +83,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy return null; case TaikoSkinComponents.TaikoExplosionMiss: - var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); if (missSprite != null) return new LegacyHitExplosion(missSprite); @@ -94,7 +91,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy case TaikoSkinComponents.TaikoExplosionOk: case TaikoSkinComponents.TaikoExplosionGreat: - var hitName = getHitName(taikoComponent.Component); var hitSprite = this.GetAnimation(hitName, true, false); @@ -126,7 +122,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy } } - return Skin.GetDrawableComponent(component); + return base.GetDrawableComponent(component); } private string getHitName(TaikoSkinComponents component) @@ -149,13 +145,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public override ISample GetSample(ISampleInfo sampleInfo) { if (sampleInfo is HitSampleInfo hitSampleInfo) - return Skin.GetSample(new LegacyTaikoSampleInfo(hitSampleInfo)); + return base.GetSample(new LegacyTaikoSampleInfo(hitSampleInfo)); return base.GetSample(sampleInfo); } - public override IBindable GetConfig(TLookup lookup) => Skin.GetConfig(lookup); - private class LegacyTaikoSampleInfo : HitSampleInfo { public LegacyTaikoSampleInfo(HitSampleInfo sampleInfo) From f20146d446d492ef9a1fa2036db0bc2dc27108b4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 12:58:38 +0300 Subject: [PATCH 280/520] Fix potentially adding null skin sources --- osu.Game/Skinning/SkinProvidingContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index c9bb3a6ec4..315571e79b 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -51,10 +51,11 @@ namespace osu.Game.Skinning /// /// Constructs a new initialised with a single skin source. /// - public SkinProvidingContainer(ISkin skin) + public SkinProvidingContainer([CanBeNull] ISkin skin) : this() { - SkinSources.Add(skin); + if (skin != null) + SkinSources.Add(skin); } /// From 7bb27bfd0e34a20f0beeb19a86f97fc0faa92a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Jun 2021 12:14:28 +0200 Subject: [PATCH 281/520] Add test scene for hidden mod --- .../Mods/TaikoModTestScene.cs | 12 ++++++++++ .../Mods/TestSceneTaikoModHidden.cs | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs new file mode 100644 index 0000000000..3090facf8c --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs @@ -0,0 +1,12 @@ +// 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.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests.Mods +{ + public abstract class TaikoModTestScene : ModTestScene + { + protected sealed override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs new file mode 100644 index 0000000000..7abbb9d186 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Rulesets.Taiko.Mods; + +namespace osu.Game.Rulesets.Taiko.Tests.Mods +{ + public class TestSceneTaikoModHidden : TaikoModTestScene + { + [Test] + public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData + { + Mod = new TaikoModHidden(), + Autoplay = true, + PassCondition = checkSomeAutoplayHits + }); + + private bool checkSomeAutoplayHits() + => Player.ScoreProcessor.JudgedHits >= 4 + && Player.Results.All(result => result.Type == result.Judgement.MaxResult); + } +} From a506f2a77699f955cd5b985f2ea4009810f76493 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Fri, 11 Jun 2021 06:22:24 -0400 Subject: [PATCH 282/520] Revert rename of lambda variables --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index ccfb0cb6e0..de9a7273c5 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -186,14 +186,14 @@ namespace osu.Game.Skinning.Editor yield return new OsuMenuItem("Anchor") { - Items = createAnchorItems((i, a) => i.UsesFixedAnchor && ((Drawable)i).Anchor == a, applyFixedAnchors) + Items = createAnchorItems((d, a) => d.UsesFixedAnchor && ((Drawable)d).Anchor == a, applyFixedAnchors) .Prepend(closestItem) .ToArray() }; yield return new OsuMenuItem("Origin") { - Items = createAnchorItems((i, o) => ((Drawable)i).Origin == o, applyOrigins).ToArray() + Items = createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray() }; foreach (var item in base.GetContextMenuItemsForSelection(selection)) From e194f8b34a4dcd8402d717afa0435cedf35d5235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Jun 2021 12:09:40 +0200 Subject: [PATCH 283/520] Replace lifetime workaround with explicit set --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index b837866c4d..bf028fa007 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -7,7 +7,9 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.Mods @@ -70,9 +72,13 @@ namespace osu.Game.Rulesets.Taiko.Mods using (hitObject.BeginAbsoluteSequence(start)) { - // With 0 opacity the object is dying, and if I set a lifetime other issues appear... - // Ideally these need to be fixed, but I lack the knowledge to do it, and this is good enough anyway. - hitObject.FadeTo(0.0005f, duration); + hitObject.FadeOut(duration); + + // DrawableHitObject sets LifetimeEnd to LatestTransformEndTime if it isn't manually changed. + // in order for the object to not be killed before its actual end time (as the latest transform ends earlier), set lifetime end explicitly. + hitObject.LifetimeEnd = state == ArmedState.Idle + ? hitObject.HitObject.GetEndTime() + hitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + : hitObject.HitStateUpdateTime; } } From 635300b3115f721188ca3ab6593b5af0cc99c921 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Fri, 11 Jun 2021 06:28:30 -0400 Subject: [PATCH 284/520] Recalculate closest anchor when origin is changed --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index de9a7273c5..dedab1f5f5 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -240,6 +240,10 @@ namespace osu.Game.Skinning.Editor var previousOrigin = drawable.OriginPosition; drawable.Origin = origin; drawable.Position += drawable.OriginPosition - previousOrigin; + + if (item.UsesFixedAnchor) continue; + + applyClosestAnchor(drawable); } } From 15d3b4444d5dc5d1f3523bf5666d4a60366f6641 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 11 Jun 2021 19:34:54 +0900 Subject: [PATCH 285/520] Rename `HoverSounds` and `HoverClickSounds` samples --- osu.Game/Graphics/UserInterface/HoverClickSounds.cs | 2 +- osu.Game/Graphics/UserInterface/HoverSounds.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index c1963ce62d..f6f2b270e6 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -45,7 +45,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio) { - sampleClick = audio.Samples.Get($@"UI/generic-select{SampleSet.GetDescription()}"); + sampleClick = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-select"); } } } diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index f2e4c6d013..60c9c36be3 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -31,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio, SessionStatics statics) { - sampleHover = audio.Samples.Get($@"UI/generic-hover{SampleSet.GetDescription()}"); + sampleHover = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-hover"); } public override void PlayHoverSample() @@ -43,19 +43,19 @@ namespace osu.Game.Graphics.UserInterface public enum HoverSampleSet { - [Description("")] + [Description("default")] Loud, - [Description("-soft")] + [Description("soft")] Normal, - [Description("-softer")] + [Description("softer")] Soft, - [Description("-toolbar")] + [Description("toolbar")] Toolbar, - [Description("-songselect")] + [Description("songselect")] SongSelect } } From a76eaeb52d7fccc6ff36fbd7727461392df8e9ff Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Fri, 11 Jun 2021 06:51:12 -0400 Subject: [PATCH 286/520] Make `getTieredComponent` local --- .../Skinning/Editor/SkinSelectionHandler.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index dedab1f5f5..c2db79b9ef 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -287,6 +287,17 @@ namespace osu.Game.Skinning.Editor var result = default(Anchor); + static Anchor getTieredComponent(float component, Anchor tier0, Anchor tier1, Anchor tier2) + { + if (component >= 2 / 3f) + return tier2; + + if (component >= 1 / 3f) + return tier1; + + return tier0; + } + result |= getTieredComponent(absolutePosition.X / factor.X, Anchor.x0, Anchor.x1, Anchor.x2); result |= getTieredComponent(absolutePosition.Y / factor.Y, Anchor.y0, Anchor.y1, Anchor.y2); @@ -310,17 +321,6 @@ namespace osu.Game.Skinning.Editor return result; } - private static Anchor getTieredComponent(float component, Anchor tier0, Anchor tier1, Anchor tier2) - { - if (component >= 2 / 3f) - return tier2; - - if (component >= 1 / 3f) - return tier1; - - return tier0; - } - private static void applyAnchor(Drawable drawable, Anchor anchor) { if (anchor == drawable.Anchor) return; From 6e181a6b6327658e4139cefc2d723eaf42e930c2 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Fri, 11 Jun 2021 06:53:04 -0400 Subject: [PATCH 287/520] Rename parameters of `getTieredComponent` --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index c2db79b9ef..048ba6556b 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -287,15 +287,15 @@ namespace osu.Game.Skinning.Editor var result = default(Anchor); - static Anchor getTieredComponent(float component, Anchor tier0, Anchor tier1, Anchor tier2) + static Anchor getTieredComponent(float component, Anchor anchor0, Anchor anchor1, Anchor anchor2) { if (component >= 2 / 3f) - return tier2; + return anchor2; if (component >= 1 / 3f) - return tier1; + return anchor1; - return tier0; + return anchor0; } result |= getTieredComponent(absolutePosition.X / factor.X, Anchor.x0, Anchor.x1, Anchor.x2); From 1bc8460902a1e230ef2015717d9ac92fb8a1878f Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Fri, 11 Jun 2021 06:53:40 -0400 Subject: [PATCH 288/520] Rename `getTieredComponent` to `getAnchorFromPosition` Also rename parameter `component` to `xOrY`. --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 048ba6556b..295b1377ed 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -287,19 +287,19 @@ namespace osu.Game.Skinning.Editor var result = default(Anchor); - static Anchor getTieredComponent(float component, Anchor anchor0, Anchor anchor1, Anchor anchor2) + static Anchor getAnchorFromPosition(float xOrY, Anchor anchor0, Anchor anchor1, Anchor anchor2) { - if (component >= 2 / 3f) + if (xOrY >= 2 / 3f) return anchor2; - if (component >= 1 / 3f) + if (xOrY >= 1 / 3f) return anchor1; return anchor0; } - result |= getTieredComponent(absolutePosition.X / factor.X, Anchor.x0, Anchor.x1, Anchor.x2); - result |= getTieredComponent(absolutePosition.Y / factor.Y, Anchor.y0, Anchor.y1, Anchor.y2); + result |= getAnchorFromPosition(absolutePosition.X / factor.X, Anchor.x0, Anchor.x1, Anchor.x2); + result |= getAnchorFromPosition(absolutePosition.Y / factor.Y, Anchor.y0, Anchor.y1, Anchor.y2); return result; } From c9b4f9eb717e9d7d36a9a3d8e502216bac529296 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Fri, 11 Jun 2021 06:55:47 -0400 Subject: [PATCH 289/520] Make `getOriginPositionFromQuad` local --- .../Skinning/Editor/SkinSelectionHandler.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 295b1377ed..cd9e82997c 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -281,6 +281,23 @@ namespace osu.Game.Skinning.Editor if (parent == null) return drawable.Anchor; + static Vector2 getOriginPositionFromQuad(in Quad quad, Anchor origin) + { + var result = quad.TopLeft; + + if (origin.HasFlagFast(Anchor.x2)) + result.X += quad.Width; + else if (origin.HasFlagFast(Anchor.x1)) + result.X += quad.Width / 2f; + + if (origin.HasFlagFast(Anchor.y2)) + result.Y += quad.Height; + else if (origin.HasFlagFast(Anchor.y1)) + result.Y += quad.Height / 2f; + + return result; + } + var screenPosition = getOriginPositionFromQuad(drawable.ScreenSpaceDrawQuad, drawable.Origin); var absolutePosition = parent.ToLocalSpace(screenPosition); var factor = parent.RelativeToAbsoluteFactor; @@ -304,23 +321,6 @@ namespace osu.Game.Skinning.Editor return result; } - private static Vector2 getOriginPositionFromQuad(in Quad quad, Anchor origin) - { - var result = quad.TopLeft; - - if (origin.HasFlagFast(Anchor.x2)) - result.X += quad.Width; - else if (origin.HasFlagFast(Anchor.x1)) - result.X += quad.Width / 2f; - - if (origin.HasFlagFast(Anchor.y2)) - result.Y += quad.Height; - else if (origin.HasFlagFast(Anchor.y1)) - result.Y += quad.Height / 2f; - - return result; - } - private static void applyAnchor(Drawable drawable, Anchor anchor) { if (anchor == drawable.Anchor) return; From a6774eb5b52a9f0272da5b9de51644fee4e44710 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Fri, 11 Jun 2021 06:59:00 -0400 Subject: [PATCH 290/520] Inline `getOriginPositionFromQuad` --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index cd9e82997c..8e1f0ce7a3 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -281,24 +281,21 @@ namespace osu.Game.Skinning.Editor if (parent == null) return drawable.Anchor; - static Vector2 getOriginPositionFromQuad(in Quad quad, Anchor origin) + var screenPosition = drawable.ScreenSpaceDrawQuad.TopLeft; { - var result = quad.TopLeft; + var origin = drawable.Origin; if (origin.HasFlagFast(Anchor.x2)) - result.X += quad.Width; + screenPosition.X += drawable.ScreenSpaceDrawQuad.Width; else if (origin.HasFlagFast(Anchor.x1)) - result.X += quad.Width / 2f; + screenPosition.X += drawable.ScreenSpaceDrawQuad.Width / 2f; if (origin.HasFlagFast(Anchor.y2)) - result.Y += quad.Height; + screenPosition.Y += drawable.ScreenSpaceDrawQuad.Height; else if (origin.HasFlagFast(Anchor.y1)) - result.Y += quad.Height / 2f; - - return result; + screenPosition.Y += drawable.ScreenSpaceDrawQuad.Height / 2f; } - var screenPosition = getOriginPositionFromQuad(drawable.ScreenSpaceDrawQuad, drawable.Origin); var absolutePosition = parent.ToLocalSpace(screenPosition); var factor = parent.RelativeToAbsoluteFactor; From 0c8851f4b7a39ff14136cea8fdcb30f4b60a88a9 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Fri, 11 Jun 2021 07:06:22 -0400 Subject: [PATCH 291/520] Extract `drawable.ScreenSpaceDrawQuad` to a variable --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 8e1f0ce7a3..c2ad08f0dc 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -281,19 +281,22 @@ namespace osu.Game.Skinning.Editor if (parent == null) return drawable.Anchor; - var screenPosition = drawable.ScreenSpaceDrawQuad.TopLeft; + Vector2 screenPosition; { + var quad = drawable.ScreenSpaceDrawQuad; var origin = drawable.Origin; + screenPosition = quad.TopLeft; + if (origin.HasFlagFast(Anchor.x2)) - screenPosition.X += drawable.ScreenSpaceDrawQuad.Width; + screenPosition.X += quad.Width; else if (origin.HasFlagFast(Anchor.x1)) - screenPosition.X += drawable.ScreenSpaceDrawQuad.Width / 2f; + screenPosition.X += quad.Width / 2f; if (origin.HasFlagFast(Anchor.y2)) - screenPosition.Y += drawable.ScreenSpaceDrawQuad.Height; + screenPosition.Y += quad.Height; else if (origin.HasFlagFast(Anchor.y1)) - screenPosition.Y += drawable.ScreenSpaceDrawQuad.Height / 2f; + screenPosition.Y += quad.Height / 2f; } var absolutePosition = parent.ToLocalSpace(screenPosition); From 6d2b5252c63540260104891ad31e829fd05e56c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Jun 2021 13:07:09 +0200 Subject: [PATCH 292/520] Attempt to reword setting to be more understandable --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index bf028fa007..00fcf5fa59 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Mods private BeatmapDifficulty difficulty; private ControlPointInfo controlPointInfo; - [SettingSource("Hide Time", "Multiplies the time the note stays hidden")] + [SettingSource("Fade-out Time", "The bigger this multiplier is, the sooner the notes will start fading out")] public BindableNumber VisibilityMod { get; } = new BindableDouble { MinValue = 0.5, From 4f80a3b66d0e61a9f5049ea52d7150e5eda23248 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 11 Jun 2021 20:14:35 +0900 Subject: [PATCH 293/520] Add fallback-to-default logic for HoverSounds and HoverClickSounds --- osu.Game/Graphics/UserInterface/HoverClickSounds.cs | 2 +- osu.Game/Graphics/UserInterface/HoverSounds.cs | 4 ++-- osu.Game/Graphics/UserInterface/OsuButton.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index f6f2b270e6..61e0266372 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -45,7 +45,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio) { - sampleClick = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-select"); + sampleClick = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-select") ?? audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select"); } } } diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index 60c9c36be3..e17128ff83 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -31,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio, SessionStatics statics) { - sampleHover = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-hover"); + sampleHover = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-hover") ?? audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-hover"); } public override void PlayHoverSample() @@ -44,7 +44,7 @@ namespace osu.Game.Graphics.UserInterface public enum HoverSampleSet { [Description("default")] - Loud, + Default, [Description("soft")] Normal, diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index a22c837080..1bd193e247 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -49,7 +49,7 @@ namespace osu.Game.Graphics.UserInterface protected Box Background; protected SpriteText SpriteText; - public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Loud) + public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Default) { Height = 40; From 0b95d07390c95fc6242b53592b854d91373cbd07 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 11 Jun 2021 20:42:10 +0900 Subject: [PATCH 294/520] Change 'default' hover/click samples into 'button' samples and make 'soft' the new 'default' --- osu.Game/Graphics/Containers/OsuClickableContainer.cs | 2 +- osu.Game/Graphics/UserInterface/HoverClickSounds.cs | 2 +- osu.Game/Graphics/UserInterface/HoverSounds.cs | 6 +++--- osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs | 1 + osu.Game/Graphics/UserInterface/OsuButton.cs | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 1f31e4cdda..60ded8952d 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -19,7 +19,7 @@ namespace osu.Game.Graphics.Containers protected virtual HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet); - public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Normal) + public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Default) { this.sampleSet = sampleSet; } diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 61e0266372..3273482162 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -28,7 +28,7 @@ namespace osu.Game.Graphics.UserInterface /// Array of button codes which should trigger the click sound. /// If this optional parameter is omitted or set to null, the click sound will only be played on left click. /// - public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal, MouseButton[] buttons = null) + public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Default, MouseButton[] buttons = null) : base(sampleSet) { this.buttons = buttons ?? new[] { MouseButton.Left }; diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index e17128ff83..ea81ef7d14 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -22,7 +22,7 @@ namespace osu.Game.Graphics.UserInterface protected readonly HoverSampleSet SampleSet; - public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) + public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Default) { SampleSet = sampleSet; RelativeSizeAxes = Axes.Both; @@ -46,8 +46,8 @@ namespace osu.Game.Graphics.UserInterface [Description("default")] Default, - [Description("soft")] - Normal, + [Description("button")] + Button, [Description("softer")] Soft, diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index cfcf034d1c..70a107ca04 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -44,6 +44,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box hover; public OsuAnimatedButton() + : base(HoverSampleSet.Button) { base.Content.Add(content = new Container { diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 1bd193e247..cd9ca9f87f 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -49,7 +49,7 @@ namespace osu.Game.Graphics.UserInterface protected Box Background; protected SpriteText SpriteText; - public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Default) + public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Button) { Height = 40; From 876a357bf29eedd693804a3b6c75f3861f7aad6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Jun 2021 13:55:38 +0200 Subject: [PATCH 295/520] Add support for animated colour fill in new style legacy health bar --- osu.Game/Skinning/LegacyHealthDisplay.cs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 9d3bafd0b1..d463df5f80 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -148,9 +148,9 @@ namespace osu.Game.Skinning } } - internal class LegacyOldStyleFill : LegacyHealthPiece + internal abstract class LegacyFill : LegacyHealthPiece { - public LegacyOldStyleFill(ISkin skin) + protected LegacyFill(ISkin skin) { // required for sizing correctly.. var firstFrame = getTexture(skin, "colour-0"); @@ -166,23 +166,25 @@ namespace osu.Game.Skinning Size = new Vector2(firstFrame.DisplayWidth, firstFrame.DisplayHeight); } - Position = new Vector2(3, 10) * 1.6f; Masking = true; } } - internal class LegacyNewStyleFill : LegacyHealthPiece + internal class LegacyOldStyleFill : LegacyFill + { + public LegacyOldStyleFill(ISkin skin) + : base(skin) + { + Position = new Vector2(3, 10) * 1.6f; + } + } + + internal class LegacyNewStyleFill : LegacyFill { public LegacyNewStyleFill(ISkin skin) + : base(skin) { - InternalChild = new Sprite - { - Texture = getTexture(skin, "colour"), - }; - - Size = InternalChild.Size; Position = new Vector2(7.5f, 7.8f) * 1.6f; - Masking = true; } protected override void Update() From 550d566bf979521e86a16d62db1eb630ee60aca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Jun 2021 14:03:21 +0200 Subject: [PATCH 296/520] Simplify member access --- osu.Game/Skinning/LegacyHealthDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index d463df5f80..1da80f6613 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -162,7 +162,7 @@ namespace osu.Game.Skinning } else { - InternalChild = skin.GetAnimation("scorebar-colour", true, true, startAtCurrentTime: false, applyConfigFrameRate: true) ?? Drawable.Empty(); + InternalChild = skin.GetAnimation("scorebar-colour", true, true, startAtCurrentTime: false, applyConfigFrameRate: true) ?? Empty(); Size = new Vector2(firstFrame.DisplayWidth, firstFrame.DisplayHeight); } From d3a255fd8113eafeaa6de763ac8bcb99a85d6e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Jun 2021 14:21:54 +0200 Subject: [PATCH 297/520] Add animated assets for legacy health display test --- .../Resources/special-skin/scorebar-bg.png | Bin 0 -> 250 bytes .../Resources/special-skin/scorebar-colour-0.png | Bin 0 -> 1285 bytes .../Resources/special-skin/scorebar-colour-1.png | Bin 0 -> 1288 bytes .../Resources/special-skin/scorebar-colour-2.png | Bin 0 -> 1287 bytes .../Resources/special-skin/scorebar-colour-3.png | Bin 0 -> 1286 bytes .../Resources/special-skin/scorebar-marker.png | Bin 0 -> 126 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Tests/Resources/special-skin/scorebar-bg.png create mode 100644 osu.Game.Tests/Resources/special-skin/scorebar-colour-0.png create mode 100644 osu.Game.Tests/Resources/special-skin/scorebar-colour-1.png create mode 100644 osu.Game.Tests/Resources/special-skin/scorebar-colour-2.png create mode 100644 osu.Game.Tests/Resources/special-skin/scorebar-colour-3.png create mode 100644 osu.Game.Tests/Resources/special-skin/scorebar-marker.png diff --git a/osu.Game.Tests/Resources/special-skin/scorebar-bg.png b/osu.Game.Tests/Resources/special-skin/scorebar-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..1a25274ed8bb9a3a5ea2cfd01445141b5f774292 GIT binary patch literal 250 zcmeAS@N?(olHy`uVBq!ia0y~yU}6Wd)j8OJ21sKV{?Q5ip7re$fgPYWH+;45_&F_S!~X1_c4;jR%s> zoJ$F)cW<6B!PaS33=>cZe(+&qTGQ{eO=}nqWIz7!efLTRhC&904rT@i83u+&3=9J7 m3=I|x3`ZCl6!>r(w2Ig9Ig`kdJJ$<9YCK*2T-G@yGywqnV>P1y literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/special-skin/scorebar-colour-0.png b/osu.Game.Tests/Resources/special-skin/scorebar-colour-0.png new file mode 100644 index 0000000000000000000000000000000000000000..3c15449b039fe44b372fdb9b538be1601f6655d6 GIT binary patch literal 1285 zcmV+g1^W7lP)000vR1^@s6X5jw>00006VoOIv00000 z008+zyMF)x010qNS#tmYE+YT{E+YYWr9XB6000McNliruTa zK~#9!?b*MN9n}@U@$WfzX5M@I|jp zl!++lkdTmLBrI@ZyvW9Dd;NaS%)RGO%mX7jL=i1IpX$zNW_0Jw`B4mn5JCtcgb+dq zA%qY@2qAoo@5#C)fG-!+(J&*^JZ0lD1vpW)u-Xa1m4-Trm}PwkB*% zD(a>tC8bJ^Z zQ3+Hk{Nd6rvlg!3YB@X@adc&myEEtdac12sDv-@-t)~kgCMixZ?2q zgeEDe8cJu`6&BeLb?!fGSPsYbMYOR=6hB}&PdD#jdo-u-GbpGibzPHGNlEC7(RT$9%%R|j>*gDP;HU^qqcpXG2<}eF z&N>^V4EI(FRnj5vajY1nfMJ{38|r@ZR+z!49{QkT!+T_>;J8~6_q+f2=z|gi{WtK3 z|MrMN2qA?3r?>z5-o<}@^uc#O9&3aogb+eFd(22bKaPPOLI@$8K_wSrpob7b2xr_c z|NX{54+|EL?MI_ vLI@#*5JCtcgb+dqA%qY@2qA+nqUV>8`c00000NkvXXu0mjfBN9_U literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/special-skin/scorebar-colour-1.png b/osu.Game.Tests/Resources/special-skin/scorebar-colour-1.png new file mode 100644 index 0000000000000000000000000000000000000000..a444723ef4023391257479a66c443a3e593dd019 GIT binary patch literal 1288 zcmV+j1^4=iP)000vR1^@s6X5jw>00006VoOIv00000 z008+zyMF)x010qNS#tmYE+YT{E+YYWr9XB6000McNliruXb2&M5JCtc zgb+dqA%qY@2qAUE0-!>c&f&-AxY3p+=>}ZQktZggQ~EzT{CWKN-4O*sICCvWZvQisj5&_9?VAIf9s;4hTqY1&uW`NUK=7-gqB#A4R_qlLx!oj%_hI4Y-Qmc@v#MZdt z@ce`(DXAJtXW11N*${Oe{Lr$Px6GC;i*-hUzBk^z_6@gBmQ)Q%-P33(V8{g%G&%FxoTjN5jkY;DdV!lauhaF03MfLC zdn{*C5{hL`A1$zAOs5mF^@s~e1WiirdobJNVvkTrRYOyaX#198J#|Wyk}*}1SGYMM zLNVv+)jxCe$IJZlU+*Dm2ud+SR7faVCq|<&nuOWxlv14ee8K*?9mE||#oc)2#UqZ7 zzvu4#6GqL1R42OilDzreBFe#ZhbzxKjR0nbs&nz+fKe?xm@W9~yE_=pg}vRMb4^un zE0mJ??%qSdxpMhQ9An*ee17XLx9&dR`R6Y4#n<0rRyIvcQJ|QyTDOcx4XQfiu|rcO zUVZUd{_@guT)Hsj>BIBvPbX9gZPzoKuju+hw!)-om~PcnqX|_dtR9{+-5GQD`x&ar ze6{A^U)<;Z!zCxDOV)jlnGN+(h92RY#J6IM#}gLoCF}W;zrOl1D)8xN|6$!_%pC*z zuF!TpW<%dC#XyC3Nr9r7N>icty}wyC|L?l*6R8I0~ryM|KL1yA^T2_u>2Rlo;s0 zf`9wAM-)N`A^ev9_U4xtKYIV2J3k$3gd~IzLO6TONdJ2r13iQgLO6p;F2q0&A%qal zxc~q68v{Lr5JLDJh=CqL2qBz75(7Pi5JEWn`fM@KLkJ;+vu@Sq80aB{5W-n!g+~#E y5JCtcgb+dqA%qY@2qA21sKV{?Q;n6f@tTAC=V433S;uunK>+Mb3e%Ys@2R{C{ zJb&g+_4lXiu4|u3N9aoU*_lVy_qJANF|6aOalO})W#sl-%=rHAD8-x_5k(!u zoVz-s4HtWQO%^E*5G~u5eazQaleJvps;I)8GtqYxLPb}dYOXxJ%Xp1Q+4k%^MzPm7 zM|}NqG{Z#E;wDdl)#;k;QIk~)-=4A)Kj&*r`_C?y78RGh407O=ihsD?&PFZLORWn2Xj4m1aB)I-j1Gpy!B1L_AQwYiS_S{Y&@rIJ00Siv}^bAhr#|uuj`LHGYbT) zEb!l9y)TnHt#7yTDuv7)0YZyRm3G}M{hBOXm{YvIL1Gnel&MG17OBNa5B_d&Jg>m& zSdiG!o?gIQUvo}Xk@XI*&jGHmsxAp8XT2E04%*6L-#+I`eFMh81_p@}@y#Pkn z7e)O~eY~7Do_V>LHLT}liPJW@LdDJno+oSh(`LuK5#(B#m9a`N_kPft<$j$(M$5cD zahbcjXgu5V>lL@m--lf9_x=}L)V4sx*rl~)QE8Hi)MC#WXZ^)fz59S=49$(-5 zFy_YXzlPUCShqWA&RqZAmSs+M_`D;A*J6*0Z+~C(`yKPV>Tg`;`+wP|rCvTF!rCRd zy7Y9#l?AJu<*ZFYA8i$1|7rR3c~393YEI`rzuA6nfaaFi^Evw#z2?5jHF;U$uF&9K zwUDVBswz&Eq@TH)pT0ut^VM1P_xGIt+wSe>H+{`-Ib-Qpi+OAhnRn(NmFQR3x#oTC zvub_a+tiQ;o9q8hFP&~!&huio)9tS_cx$(dFpGZsw(N}D*_Xe0xU{@tud9Rx3H?g? z63fU^tYPtJ*H1PD3oWP18op~4nsx_o+I-GU^YGrOPE$)xRLf|)X(rrQ_N{H#{XL>- z2CLfbUflQ~cT>~iUD%Po_wL*MTX4kTzvv(P_0=xG40|a5rT+W4eLueU+y8jhp4>YD zl!w#%C0{N-Zg{~Nn5B(ZJaOXr0u+4M^ZV!i`w}len%+A=gm@-F_&2YocQ?ubOMp40 stIdu(@B`JHv(mQd(m*Q_VB8zopr0M~s-fB*mh literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/special-skin/scorebar-colour-3.png b/osu.Game.Tests/Resources/special-skin/scorebar-colour-3.png new file mode 100644 index 0000000000000000000000000000000000000000..a3a5ca4716d3898929332a54b631ee604c017390 GIT binary patch literal 1286 zcmeAS@N?(olHy`uVBq!ia0y~yVEh7P3v;jm$+QRmS%4H21sKV{?Q;W1RpeUik$z%tp>#WAGf*4vx5{jyI*4}AP@ zdH&3u>hDk2UDrO7)SS}85V@wMXMuyFtjQDZB@36FJ^mngNvDP;vuCHvLq1MU$!MlV z;m59KakIbH&Yk((QvGO5!4$qgm+o`xtI|x2tDc>y)MRtwP;3!EB^VS0o8sTUwf~!X zYkJ+Ph7P4gE3_U&==m$X?)$x=j9t(%#L~jX-*%PdzUroq2Nto>JAVAx{O9N2@)>^H zcmBKSYxna-uz>39!d(%UwrSt#QDWlIYwBv&UUul*wU)J>u6M62SrVc-(M7^vDrC;C zpl{{dBXy$l?(8^SbT{y7^0BisPG75v6rD%qgU&6{kKNh51-AQ zcDPy4J^b4a`{F#g^lN(;G2NVVt;$Q#L9ozE?@3UB`L%Azd)qstcg7Ze4LYiQ{#t~Y z3g@oQXv4*xUXw+N14PTVWgqkP)nqM~xGJhJ=S=h+g;3E|rw&)1-etT-q-=Zk9i!Om znm!tvR6E2(VkwwTwiofRgv`$ug?Llu&OQzCTG1F3r@~F|IEbqXvUVa7cXkx*;U%S&OF3H zaMzt=%gK`hbc%mfG>Rr$&7H8;J;p;RBdF+p^G!AJ>K3O}QnRvLZf_TPy)t;wl%AE7 z_8dAZDA;A0zwf7(yMFAm_4U847qu-AF?MNfSyY;2BDL6a##ukjqchKoznOpA@s45W zonG^GvV}E&l)uZ(G00L|Vs&xt^e1P(iv{v6vyQ3#XUSq<95T0?oxLaP;lrGR+wcEx zco=iz_FvWOA*|b-G-tklZ_hMm_4N7246nr=zh3^X@cBLGxSEGX_iMiApE%TUfgV$Jk5 z&%OWsxb}L*f#>lxM|Yi`Va@g>cH!HqGj02#yBfKwtAo?}pMR-kV-;PxE_;%eCTFeT zuXPMfmLf8RcWamhWJDKcPFWTvaPY3?&B*k{Qy$*)TIgl<#I{d#v4}xVaJBQ@{Cix+ z5?7t&OL8ju--yVRYd@;{FE3vok+k4HTb*pb1~9+=sIU6o{9W(g=g0i@KYm6is{k|c zp_|EWzurHVcmZNdh7>8XRRQyM!Lh&Z_uq#IzIWjH0?g|Vc_u;lH?OC6H_8Ds|D4j* tX2%`)ff~@23MQ{KB literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/special-skin/scorebar-marker.png b/osu.Game.Tests/Resources/special-skin/scorebar-marker.png new file mode 100644 index 0000000000000000000000000000000000000000..b5af0b2148832b745caf04c4e2b5103efa43c913 GIT binary patch literal 126 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}blwj^(N7a$D;Kb?2i11Zh|kH}&M z20djEW~^9hUj`IpFY)wsWq-=X%_F6M;8)Hypb)pGi(?4K_2dKwCI$vp2F6Efth0f_ N44$rjF6*2UngDK&89D#} literal 0 HcmV?d00001 From dca001a72d6e079e22cf3c8f0e60b3ebd2909a48 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 22:22:47 +0900 Subject: [PATCH 298/520] Upgraed localisation analyser packages --- .config/dotnet-tools.json | 4 ++-- osu.Game/osu.Game.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index b51ecb4f7e..84673fe2ba 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -33,10 +33,10 @@ ] }, "ppy.localisationanalyser.tools": { - "version": "2021.524.0", + "version": "2021.608.0", "commands": [ "localisation" ] } } -} \ No newline at end of file +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2ee8ed527f..ed299456de 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 108a3deb27382b833f297abf79e63a53e8da1b4d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 16:26:53 +0300 Subject: [PATCH 299/520] Also handle null `Ruleset.CreateLegacySkinProvider` values Let's just go this way for now, maybe it's a better choice to always create transformers and disallow null, but it's too much work and out of scope at this point --- .../Skinning/RulesetSkinProvidingContainer.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index c57522726d..b6a3bd7cda 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Skinning Ruleset = ruleset; Beatmap = beatmap; - InternalChild = new BeatmapSkinProvidingContainer(beatmapSkin == null ? null : ruleset.CreateLegacySkinProvider(beatmapSkin, beatmap)) + InternalChild = new BeatmapSkinProvidingContainer(GetRulesetTransformedSkin(beatmapSkin)) { Child = Content = new Container { @@ -54,13 +54,25 @@ namespace osu.Game.Skinning { SkinSources.Clear(); - SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.CurrentSkin.Value, Beatmap)); + SkinSources.Add(GetRulesetTransformedSkin(skinManager.CurrentSkin.Value)); // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. if (skinManager.CurrentSkin.Value is LegacySkin) - SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, Beatmap)); + SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultLegacySkin)); - SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, Beatmap)); + SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultSkin)); + } + + protected ISkin GetRulesetTransformedSkin(ISkin skin) + { + if (skin == null) + return null; + + var rulesetTransformed = Ruleset.CreateLegacySkinProvider(skin, Beatmap); + if (rulesetTransformed != null) + return rulesetTransformed; + + return skin; } } } From 97bb3de1c9024ef77798739981ee0615a5c4c7cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 23:03:27 +0900 Subject: [PATCH 300/520] Update resources --- 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 0d3fafd19f..ba8b8b32ba 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2ee8ed527f..6d3a1d5226 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 7832aaaf2d..e382a804c8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From d6d87e1975b3cd6145328e2849f358606acc17c9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 17:35:32 +0300 Subject: [PATCH 301/520] Move collection change bind to LoadComplete Best practice anyways --- osu.Game/Skinning/SkinProvidingContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 315571e79b..ac1b4d0395 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -65,6 +65,11 @@ namespace osu.Game.Skinning protected SkinProvidingContainer() { RelativeSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); SkinSources.BindCollectionChanged(((_, args) => { From 5887b4a27cc585bb20cb3e9bb3954f19e40de3be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 23:46:42 +0900 Subject: [PATCH 302/520] Update stand-alone usage of hover/select sounds in `DrawableOsuMenuItem` --- .../UserInterface/DrawableOsuMenuItem.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index 8df2c1c2fd..fea84998cf 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,9 +22,6 @@ namespace osu.Game.Graphics.UserInterface private const int text_size = 17; private const int transition_length = 80; - private Sample sampleClick; - private Sample sampleHover; - private TextContainer text; public DrawableOsuMenuItem(MenuItem item) @@ -36,12 +32,11 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio) { - sampleHover = audio.Samples.Get(@"UI/generic-hover"); - sampleClick = audio.Samples.Get(@"UI/generic-select"); - BackgroundColour = Color4.Transparent; BackgroundColourHover = Color4Extensions.FromHex(@"172023"); + AddInternal(new HoverClickSounds()); + updateTextColour(); Item.Action.BindDisabledChanged(_ => updateState(), true); @@ -84,7 +79,6 @@ namespace osu.Game.Graphics.UserInterface if (IsHovered && !Item.Action.Disabled) { - sampleHover.Play(); text.BoldText.FadeIn(transition_length, Easing.OutQuint); text.NormalText.FadeOut(transition_length, Easing.OutQuint); } @@ -95,12 +89,6 @@ namespace osu.Game.Graphics.UserInterface } } - protected override bool OnClick(ClickEvent e) - { - sampleClick.Play(); - return base.OnClick(e); - } - protected sealed override Drawable CreateContent() => text = CreateTextContainer(); protected virtual TextContainer CreateTextContainer() => new TextContainer(); From e098cac1cffb9388f5c2124eb5e0b6bac953ba20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 23:49:14 +0900 Subject: [PATCH 303/520] Minor code reformatting / moving --- .../UserInterface/HoverClickSounds.cs | 3 ++- .../Graphics/UserInterface/HoverSampleSet.cs | 25 +++++++++++++++++++ .../Graphics/UserInterface/HoverSounds.cs | 22 ++-------------- 3 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/HoverSampleSet.cs diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 3273482162..12819840e5 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -45,7 +45,8 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio) { - sampleClick = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-select") ?? audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select"); + sampleClick = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-select") + ?? audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select"); } } } diff --git a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs new file mode 100644 index 0000000000..c74ac90a4c --- /dev/null +++ b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs @@ -0,0 +1,25 @@ +// 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; + +namespace osu.Game.Graphics.UserInterface +{ + public enum HoverSampleSet + { + [Description("default")] + Default, + + [Description("button")] + Button, + + [Description("softer")] + Soft, + + [Description("toolbar")] + Toolbar, + + [Description("songselect")] + SongSelect + } +} diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index ea81ef7d14..c0ef5cb3fc 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.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 System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -31,7 +30,8 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio, SessionStatics statics) { - sampleHover = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-hover") ?? audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-hover"); + sampleHover = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-hover") + ?? audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-hover"); } public override void PlayHoverSample() @@ -40,22 +40,4 @@ namespace osu.Game.Graphics.UserInterface sampleHover.Play(); } } - - public enum HoverSampleSet - { - [Description("default")] - Default, - - [Description("button")] - Button, - - [Description("softer")] - Soft, - - [Description("toolbar")] - Toolbar, - - [Description("songselect")] - SongSelect - } } From b6947c25ec5d5c3c40e50cf4ad7e3450009a8a68 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 17:55:07 +0300 Subject: [PATCH 304/520] Fix potentially adding the same skin multiple times --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index b6a3bd7cda..8a27899e89 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -57,10 +57,11 @@ namespace osu.Game.Skinning SkinSources.Add(GetRulesetTransformedSkin(skinManager.CurrentSkin.Value)); // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. - if (skinManager.CurrentSkin.Value is LegacySkin) + if (skinManager.CurrentSkin.Value is LegacySkin && skinManager.CurrentSkin.Value != skinManager.DefaultLegacySkin) SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultLegacySkin)); - SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultSkin)); + if (skinManager.CurrentSkin.Value != skinManager.DefaultSkin) + SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultSkin)); } protected ISkin GetRulesetTransformedSkin(ISkin skin) From 8de0d33c5a36b8b9e9af4c035eb9a21caa25d3cf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 17:59:28 +0300 Subject: [PATCH 305/520] Revert "Move collection change bind to LoadComplete" This reverts commit d6d87e1975b3cd6145328e2849f358606acc17c9. Actually that broke things due to the "disableable" instances not added early enough, revert for now. --- osu.Game/Skinning/SkinProvidingContainer.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index ac1b4d0395..315571e79b 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -65,11 +65,6 @@ namespace osu.Game.Skinning protected SkinProvidingContainer() { RelativeSizeAxes = Axes.Both; - } - - protected override void LoadComplete() - { - base.LoadComplete(); SkinSources.BindCollectionChanged(((_, args) => { From d9ea8d64d4855feda54c3512b054344c86352d2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Jun 2021 00:05:49 +0900 Subject: [PATCH 306/520] Remove weird local sample logic in `ChangelogOverlay` --- osu.Game/Overlays/ChangelogOverlay.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index e7d68853ad..67d83980ef 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -25,8 +25,6 @@ namespace osu.Game.Overlays public readonly Bindable Current = new Bindable(); - private Sample sampleBack; - private List builds; protected List Streams; @@ -41,8 +39,6 @@ namespace osu.Game.Overlays { Header.Build.BindTarget = Current; - sampleBack = audio.Samples.Get(@"UI/generic-select-soft"); - Current.BindValueChanged(e => { if (e.NewValue != null) @@ -108,7 +104,6 @@ namespace osu.Game.Overlays else { Current.Value = null; - sampleBack?.Play(); } return true; From 0dbe5dd2190f030e76a875bca0845cd67856d29d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Jun 2021 00:05:49 +0900 Subject: [PATCH 307/520] Remove unused using statement --- osu.Game/Overlays/ChangelogOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 67d83980ef..a8f2e654d7 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Input.Bindings; From 121df57dca7e5f66458ac952920b927861113f0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Jun 2021 00:26:33 +0900 Subject: [PATCH 308/520] Fix focused overlays playing their "appear" sound when not necessarily changing state --- .../Containers/OsuFocusedOverlayContainer.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index c0518247a9..b9b098df80 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -107,10 +107,10 @@ namespace osu.Game.Graphics.Containers { } - private bool playedPopInSound; - protected override void UpdateState(ValueChangedEvent state) { + bool didChange = state.NewValue != state.OldValue; + switch (state.NewValue) { case Visibility.Visible: @@ -121,18 +121,15 @@ namespace osu.Game.Graphics.Containers return; } - samplePopIn?.Play(); - playedPopInSound = true; + if (didChange) + samplePopIn?.Play(); if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this); break; case Visibility.Hidden: - if (playedPopInSound) - { + if (didChange) samplePopOut?.Play(); - playedPopInSound = false; - } if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this); break; From f773ea475d08fa0758e436db9ead42e97ad227d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Jun 2021 01:37:13 +0900 Subject: [PATCH 309/520] 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 ba8b8b32ba..13d45835be 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 877ae94a5e..7eb3c84582 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -34,7 +34,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index e382a804c8..3e8facaf6e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From fe39a47797d9d754c59c86414d90bd237a02e9cb Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 12 Jun 2021 00:34:53 +0200 Subject: [PATCH 310/520] Add `OsuModSettingsTextBox` and `OsuModSettingsNumberBox` --- .../UserInterface/OsuModSettingsNumberBox.cs | 10 +++++ .../UserInterface/OsuModSettingsTextBox.cs | 44 +++++++++++++++++++ osu.Game/Overlays/Settings/SettingsTextBox.cs | 2 +- osu.Game/Rulesets/Mods/ModRandom.cs | 4 +- 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/OsuModSettingsNumberBox.cs create mode 100644 osu.Game/Graphics/UserInterface/OsuModSettingsTextBox.cs diff --git a/osu.Game/Graphics/UserInterface/OsuModSettingsNumberBox.cs b/osu.Game/Graphics/UserInterface/OsuModSettingsNumberBox.cs new file mode 100644 index 0000000000..4ec4165c0e --- /dev/null +++ b/osu.Game/Graphics/UserInterface/OsuModSettingsNumberBox.cs @@ -0,0 +1,10 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuModSettingsNumberBox : OsuModSettingsTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuModSettingsTextBox.cs b/osu.Game/Graphics/UserInterface/OsuModSettingsTextBox.cs new file mode 100644 index 0000000000..6720727b7a --- /dev/null +++ b/osu.Game/Graphics/UserInterface/OsuModSettingsTextBox.cs @@ -0,0 +1,44 @@ +// 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.Color4Extensions; +using osu.Framework.Graphics.Colour; +using osu.Framework.Input.Events; + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuModSettingsTextBox : OsuTextBox + { + private const float border_thickness = 3; + + private SRGBColour borderColourFocused; + private SRGBColour borderColourUnfocused; + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + borderColourUnfocused = colour.Gray4.Opacity(0.5f); + borderColourFocused = BorderColour; + + BorderThickness = border_thickness; + BorderColour = borderColourUnfocused; + } + + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + + BorderThickness = border_thickness; + BorderColour = borderColourFocused; + } + + protected override void OnFocusLost(FocusLostEvent e) + { + base.OnFocusLost(e); + + BorderThickness = border_thickness; + BorderColour = borderColourUnfocused; + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsTextBox.cs b/osu.Game/Overlays/Settings/SettingsTextBox.cs index 5e700a1d6b..43bc8e87f8 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -8,7 +8,7 @@ namespace osu.Game.Overlays.Settings { public class SettingsTextBox : SettingsItem { - protected override Drawable CreateControl() => new OsuTextBox + protected override Drawable CreateControl() => new OsuModSettingsTextBox { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index 3f14263420..450b2a0680 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mods } } - private readonly OsuNumberBox seedNumberBox; + private readonly OsuModSettingsNumberBox seedNumberBox; public SeedControl() { @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Mods { new Drawable[] { - seedNumberBox = new OsuNumberBox + seedNumberBox = new OsuModSettingsNumberBox { RelativeSizeAxes = Axes.X, CommitOnFocusLost = true From bb661abfa65d44f1fb15f6d351f3fbcbb8e3984a Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 12 Jun 2021 17:25:22 +0200 Subject: [PATCH 311/520] Clean up `OsuModSettingsTextBox` --- .../UserInterface/OsuModSettingsTextBox.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuModSettingsTextBox.cs b/osu.Game/Graphics/UserInterface/OsuModSettingsTextBox.cs index 6720727b7a..11b7ed33d0 100644 --- a/osu.Game/Graphics/UserInterface/OsuModSettingsTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuModSettingsTextBox.cs @@ -3,8 +3,8 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics.Colour; using osu.Framework.Input.Events; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { @@ -12,8 +12,8 @@ namespace osu.Game.Graphics.UserInterface { private const float border_thickness = 3; - private SRGBColour borderColourFocused; - private SRGBColour borderColourUnfocused; + private Color4 borderColourFocused; + private Color4 borderColourUnfocused; [BackgroundDependencyLoader] private void load(OsuColour colour) @@ -21,24 +21,27 @@ namespace osu.Game.Graphics.UserInterface borderColourUnfocused = colour.Gray4.Opacity(0.5f); borderColourFocused = BorderColour; - BorderThickness = border_thickness; - BorderColour = borderColourUnfocused; + updateBorder(); } protected override void OnFocus(FocusEvent e) { base.OnFocus(e); - BorderThickness = border_thickness; - BorderColour = borderColourFocused; + updateBorder(); } protected override void OnFocusLost(FocusLostEvent e) { base.OnFocusLost(e); + updateBorder(); + } + + private void updateBorder() + { BorderThickness = border_thickness; - BorderColour = borderColourUnfocused; + BorderColour = HasFocus ? borderColourFocused : borderColourUnfocused; } } } From 29f38804156b58aee0b6f31c71a28f7dec856b17 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 12 Jun 2021 17:34:02 +0200 Subject: [PATCH 312/520] Move classes into `SettingsTextBox` --- .../UserInterface/OsuModSettingsNumberBox.cs | 10 ---- .../UserInterface/OsuModSettingsTextBox.cs | 47 ------------------- osu.Game/Overlays/Settings/SettingsTextBox.cs | 47 +++++++++++++++++++ osu.Game/Rulesets/Mods/ModRandom.cs | 5 +- 4 files changed, 49 insertions(+), 60 deletions(-) delete mode 100644 osu.Game/Graphics/UserInterface/OsuModSettingsNumberBox.cs delete mode 100644 osu.Game/Graphics/UserInterface/OsuModSettingsTextBox.cs diff --git a/osu.Game/Graphics/UserInterface/OsuModSettingsNumberBox.cs b/osu.Game/Graphics/UserInterface/OsuModSettingsNumberBox.cs deleted file mode 100644 index 4ec4165c0e..0000000000 --- a/osu.Game/Graphics/UserInterface/OsuModSettingsNumberBox.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Graphics.UserInterface -{ - public class OsuModSettingsNumberBox : OsuModSettingsTextBox - { - protected override bool CanAddCharacter(char character) => char.IsNumber(character); - } -} diff --git a/osu.Game/Graphics/UserInterface/OsuModSettingsTextBox.cs b/osu.Game/Graphics/UserInterface/OsuModSettingsTextBox.cs deleted file mode 100644 index 11b7ed33d0..0000000000 --- a/osu.Game/Graphics/UserInterface/OsuModSettingsTextBox.cs +++ /dev/null @@ -1,47 +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 osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Input.Events; -using osuTK.Graphics; - -namespace osu.Game.Graphics.UserInterface -{ - public class OsuModSettingsTextBox : OsuTextBox - { - private const float border_thickness = 3; - - private Color4 borderColourFocused; - private Color4 borderColourUnfocused; - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - borderColourUnfocused = colour.Gray4.Opacity(0.5f); - borderColourFocused = BorderColour; - - updateBorder(); - } - - protected override void OnFocus(FocusEvent e) - { - base.OnFocus(e); - - updateBorder(); - } - - protected override void OnFocusLost(FocusLostEvent e) - { - base.OnFocusLost(e); - - updateBorder(); - } - - private void updateBorder() - { - BorderThickness = border_thickness; - BorderColour = HasFocus ? borderColourFocused : borderColourUnfocused; - } - } -} diff --git a/osu.Game/Overlays/Settings/SettingsTextBox.cs b/osu.Game/Overlays/Settings/SettingsTextBox.cs index 43bc8e87f8..4e96573538 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -1,8 +1,13 @@ // 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.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; namespace osu.Game.Overlays.Settings { @@ -14,5 +19,47 @@ namespace osu.Game.Overlays.Settings RelativeSizeAxes = Axes.X, CommitOnFocusLost = true, }; + + public class OsuModSettingsTextBox : OsuTextBox + { + private const float border_thickness = 3; + + private Color4 borderColourFocused; + private Color4 borderColourUnfocused; + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + borderColourUnfocused = colour.Gray4.Opacity(0.5f); + borderColourFocused = BorderColour; + + updateBorder(); + } + + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + + updateBorder(); + } + + protected override void OnFocusLost(FocusLostEvent e) + { + base.OnFocusLost(e); + + updateBorder(); + } + + private void updateBorder() + { + BorderThickness = border_thickness; + BorderColour = HasFocus ? borderColourFocused : borderColourUnfocused; + } + } + + public class OsuModSettingsNumberBox : OsuModSettingsTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } } } diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index 450b2a0680..7220580b9f 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods @@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.Mods } } - private readonly OsuModSettingsNumberBox seedNumberBox; + private readonly SettingsTextBox.OsuModSettingsNumberBox seedNumberBox; public SeedControl() { @@ -76,7 +75,7 @@ namespace osu.Game.Rulesets.Mods { new Drawable[] { - seedNumberBox = new OsuModSettingsNumberBox + seedNumberBox = new SettingsTextBox.OsuModSettingsNumberBox { RelativeSizeAxes = Axes.X, CommitOnFocusLost = true From c728f673d6dffc3e24d3d1b0163034922b45544e Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 12 Jun 2021 17:37:01 +0200 Subject: [PATCH 313/520] Rename classes --- osu.Game/Overlays/Settings/SettingsTextBox.cs | 6 +++--- osu.Game/Rulesets/Mods/ModRandom.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsTextBox.cs b/osu.Game/Overlays/Settings/SettingsTextBox.cs index 4e96573538..f895a66128 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -13,14 +13,14 @@ namespace osu.Game.Overlays.Settings { public class SettingsTextBox : SettingsItem { - protected override Drawable CreateControl() => new OsuModSettingsTextBox + protected override Drawable CreateControl() => new OsuSettingsTextBox { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, CommitOnFocusLost = true, }; - public class OsuModSettingsTextBox : OsuTextBox + public class OsuSettingsTextBox : OsuTextBox { private const float border_thickness = 3; @@ -57,7 +57,7 @@ namespace osu.Game.Overlays.Settings } } - public class OsuModSettingsNumberBox : OsuModSettingsTextBox + public class OsuSettingsNumberBox : OsuSettingsTextBox { protected override bool CanAddCharacter(char character) => char.IsNumber(character); } diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index 7220580b9f..cef1814ee6 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mods } } - private readonly SettingsTextBox.OsuModSettingsNumberBox seedNumberBox; + private readonly SettingsTextBox.OsuSettingsNumberBox seedNumberBox; public SeedControl() { @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Mods { new Drawable[] { - seedNumberBox = new SettingsTextBox.OsuModSettingsNumberBox + seedNumberBox = new SettingsTextBox.OsuSettingsNumberBox { RelativeSizeAxes = Axes.X, CommitOnFocusLost = true From b79d57b68c4586ca174b702b6c50396a0262bb72 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 12 Jun 2021 17:57:40 +0200 Subject: [PATCH 314/520] Move `OsuSettingsNumberBox` into `SettingsNumberBox` --- osu.Game/Overlays/Settings/SettingsNumberBox.cs | 8 ++++++-- osu.Game/Overlays/Settings/SettingsTextBox.cs | 5 ----- osu.Game/Rulesets/Mods/ModRandom.cs | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index cb7e63ae6f..20de35ed87 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -2,16 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { public class SettingsNumberBox : SettingsItem { - protected override Drawable CreateControl() => new OsuNumberBox + protected override Drawable CreateControl() => new OsuSettingsNumberBox { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, }; + + public class OsuSettingsNumberBox : SettingsTextBox.OsuSettingsTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } } } diff --git a/osu.Game/Overlays/Settings/SettingsTextBox.cs b/osu.Game/Overlays/Settings/SettingsTextBox.cs index f895a66128..efcfb0ec5b 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -56,10 +56,5 @@ namespace osu.Game.Overlays.Settings BorderColour = HasFocus ? borderColourFocused : borderColourUnfocused; } } - - public class OsuSettingsNumberBox : OsuSettingsTextBox - { - protected override bool CanAddCharacter(char character) => char.IsNumber(character); - } } } diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index cef1814ee6..49e5ec0cbc 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mods } } - private readonly SettingsTextBox.OsuSettingsNumberBox seedNumberBox; + private readonly SettingsNumberBox.OsuSettingsNumberBox seedNumberBox; public SeedControl() { @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Mods { new Drawable[] { - seedNumberBox = new SettingsTextBox.OsuSettingsNumberBox + seedNumberBox = new SettingsNumberBox.OsuSettingsNumberBox { RelativeSizeAxes = Axes.X, CommitOnFocusLost = true From ef9cb2c95836a1ff7eeffe0d46f6cc5afe72b6e7 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 12 Jun 2021 18:37:31 +0200 Subject: [PATCH 315/520] Rename nested classes --- osu.Game/Overlays/Settings/SettingsNumberBox.cs | 4 ++-- osu.Game/Overlays/Settings/SettingsTextBox.cs | 4 ++-- osu.Game/Rulesets/Mods/ModRandom.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index 20de35ed87..ca9a8e9c08 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -7,13 +7,13 @@ namespace osu.Game.Overlays.Settings { public class SettingsNumberBox : SettingsItem { - protected override Drawable CreateControl() => new OsuSettingsNumberBox + protected override Drawable CreateControl() => new NumberBox { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, }; - public class OsuSettingsNumberBox : SettingsTextBox.OsuSettingsTextBox + public class NumberBox : SettingsTextBox.TextBox { protected override bool CanAddCharacter(char character) => char.IsNumber(character); } diff --git a/osu.Game/Overlays/Settings/SettingsTextBox.cs b/osu.Game/Overlays/Settings/SettingsTextBox.cs index efcfb0ec5b..25424e85a1 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -13,14 +13,14 @@ namespace osu.Game.Overlays.Settings { public class SettingsTextBox : SettingsItem { - protected override Drawable CreateControl() => new OsuSettingsTextBox + protected override Drawable CreateControl() => new TextBox { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, CommitOnFocusLost = true, }; - public class OsuSettingsTextBox : OsuTextBox + public class TextBox : OsuTextBox { private const float border_thickness = 3; diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index 49e5ec0cbc..e0c3008ae8 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mods } } - private readonly SettingsNumberBox.OsuSettingsNumberBox seedNumberBox; + private readonly SettingsNumberBox.NumberBox seedNumberBox; public SeedControl() { @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Mods { new Drawable[] { - seedNumberBox = new SettingsNumberBox.OsuSettingsNumberBox + seedNumberBox = new SettingsNumberBox.NumberBox { RelativeSizeAxes = Axes.X, CommitOnFocusLost = true From 17347401cf29d825686ab775a471698c5fb68304 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Jun 2021 11:27:45 +0900 Subject: [PATCH 316/520] Remove unused `RankingType` enum We have `BeatmapLeaderboardScope` instead. --- osu.Game/Configuration/RankingType.cs | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 osu.Game/Configuration/RankingType.cs diff --git a/osu.Game/Configuration/RankingType.cs b/osu.Game/Configuration/RankingType.cs deleted file mode 100644 index 7701e1dd1d..0000000000 --- a/osu.Game/Configuration/RankingType.cs +++ /dev/null @@ -1,20 +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.ComponentModel; - -namespace osu.Game.Configuration -{ - public enum RankingType - { - Local, - - [Description("Global")] - Top, - - [Description("Selected Mods")] - SelectedMod, - Friends, - Country - } -} From 8cf44547802fff912ec0a9ce42032e51df1ac0f3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 11 Jun 2021 23:50:41 +0900 Subject: [PATCH 317/520] Use `Direction` enum instead of `int` The property is named `scrollingAxis` to distinguish from `direction`, which is of `ScrollingDirection` type (unfortunate name crash). --- .../Scrolling/ScrollingHitObjectContainer.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index d21f30eb30..b2c549244d 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -20,9 +20,9 @@ namespace osu.Game.Rulesets.UI.Scrolling private readonly IBindable direction = new Bindable(); /// - /// 0 for horizontal scroll, 1 for vertical scroll. + /// Whether the scrolling direction is horizontal or vertical. /// - private int scrollingAxis => direction.Value == ScrollingDirection.Left || direction.Value == ScrollingDirection.Right ? 0 : 1; + private Direction scrollingAxis => direction.Value == ScrollingDirection.Left || direction.Value == ScrollingDirection.Right ? Direction.Horizontal : Direction.Vertical; /// /// A set of top-level s which have an up-to-date layout. @@ -68,8 +68,8 @@ namespace osu.Game.Rulesets.UI.Scrolling /// public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) { - Vector2 localPosition = ToLocalSpace(screenSpacePosition); - return TimeAtPosition(localPosition[scrollingAxis], Time.Current); + Vector2 position = ToLocalSpace(screenSpacePosition); + return TimeAtPosition(scrollingAxis == Direction.Horizontal ? position.X : position.Y, Time.Current); } /// @@ -77,9 +77,9 @@ namespace osu.Game.Rulesets.UI.Scrolling /// public float PositionAtTime(double time, double currentTime) { - float pos = scrollingInfo.Algorithm.PositionAt(time, currentTime, timeRange.Value, scrollLength); - flipPositionIfRequired(ref pos); - return pos; + float position = scrollingInfo.Algorithm.PositionAt(time, currentTime, timeRange.Value, scrollLength); + flipPositionIfRequired(ref position); + return position; } /// @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.UI.Scrolling public Vector2 ScreenSpacePositionAtTime(double time) { float position = PositionAtTime(time, Time.Current); - return scrollingAxis == 0 + return scrollingAxis == Direction.Horizontal ? ToScreenSpace(new Vector2(position, DrawHeight / 2)) : ToScreenSpace(new Vector2(DrawWidth / 2, position)); } @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.UI.Scrolling return scrollingInfo.Algorithm.GetLength(startTime, endTime, timeRange.Value, scrollLength); } - private float scrollLength => DrawSize[scrollingAxis]; + private float scrollLength => scrollingAxis == Direction.Horizontal ? DrawWidth : DrawHeight; private void flipPositionIfRequired(ref float position) { @@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.UI.Scrolling if (hitObject.HitObject is IHasDuration e) { float length = LengthAtTime(hitObject.HitObject.StartTime, e.EndTime); - if (scrollingAxis == 0) + if (scrollingAxis == Direction.Horizontal) hitObject.Width = length; else hitObject.Height = length; @@ -242,7 +242,7 @@ namespace osu.Game.Rulesets.UI.Scrolling { float position = PositionAtTime(hitObject.HitObject.StartTime, currentTime); - if (scrollingAxis == 0) + if (scrollingAxis == Direction.Horizontal) hitObject.X = position; else hitObject.Y = position; From fdb09ef4d7a7a6791f887f84b4b91ed517fd2b59 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 11 Jun 2021 23:53:01 +0900 Subject: [PATCH 318/520] Simplify `flipPositionIfRequired` using `scrollLength` --- .../UI/Scrolling/ScrollingHitObjectContainer.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index b2c549244d..283d84e8df 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -114,17 +114,8 @@ namespace osu.Game.Rulesets.UI.Scrolling // We're dealing with coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position, // so when scrolling downwards the coordinates need to be flipped. - - switch (scrollingInfo.Direction.Value) - { - case ScrollingDirection.Down: - position = DrawHeight - position; - break; - - case ScrollingDirection.Right: - position = DrawWidth - position; - break; - } + if (direction.Value == ScrollingDirection.Down || direction.Value == ScrollingDirection.Right) + position = scrollLength - position; } protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) From 09f1cbde7eb13b8a72f9965ac0ee9ef933d622e3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 14 Jun 2021 12:41:44 +0900 Subject: [PATCH 319/520] Fix `TimeAtPosition` doc comment --- .../UI/Scrolling/ScrollingHitObjectContainer.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 283d84e8df..061c9aa948 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -53,19 +53,23 @@ namespace osu.Game.Rulesets.UI.Scrolling } /// - /// Given a position along the scrolling axis, return the time within this . + /// Given a position at , return the time of the object corresponding the position. /// - /// The position along the scrolling axis. - /// The time the scrolling speed is used. - public double TimeAtPosition(float position, double referenceTime) + /// + /// If there are multiple valid time values, one arbitrary time is returned. + /// + public double TimeAtPosition(float position, double currentTime) { flipPositionIfRequired(ref position); - return scrollingInfo.Algorithm.TimeAt(position, referenceTime, timeRange.Value, scrollLength); + return scrollingInfo.Algorithm.TimeAt(position, currentTime, timeRange.Value, scrollLength); } /// - /// Given a position in screen space, return the time within this . + /// Given a position at the current time in screen space, return the time of the object corresponding the position. /// + /// + /// If there are multiple valid time values, one arbitrary time is returned. + /// public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) { Vector2 position = ToLocalSpace(screenSpacePosition); From 660bf50dc7ce71115a64a4a8168826bd5cc6cf90 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 14 Jun 2021 13:10:07 +0900 Subject: [PATCH 320/520] Clarify multiple coordinate systems - Fix wrong position is set for DHOs for down/right scrolling direction. --- .../Scrolling/ScrollingHitObjectContainer.cs | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 061c9aa948..d75954d77f 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.UI.Scrolling /// private Direction scrollingAxis => direction.Value == ScrollingDirection.Left || direction.Value == ScrollingDirection.Right ? Direction.Horizontal : Direction.Vertical; + /// + /// Whether the scrolling direction is the positive-to-negative direction in the local coordinate. + /// + private bool axisInverted => direction.Value == ScrollingDirection.Down || direction.Value == ScrollingDirection.Right; + /// /// A set of top-level s which have an up-to-date layout. /// @@ -58,10 +63,10 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// If there are multiple valid time values, one arbitrary time is returned. /// - public double TimeAtPosition(float position, double currentTime) + public double TimeAtPosition(float localPosition, double currentTime) { - flipPositionIfRequired(ref position); - return scrollingInfo.Algorithm.TimeAt(position, currentTime, timeRange.Value, scrollLength); + float scrollPosition = axisInverted ? scrollLength - localPosition : localPosition; + return scrollingInfo.Algorithm.TimeAt(scrollPosition, currentTime, timeRange.Value, scrollLength); } /// @@ -72,8 +77,8 @@ namespace osu.Game.Rulesets.UI.Scrolling /// public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) { - Vector2 position = ToLocalSpace(screenSpacePosition); - return TimeAtPosition(scrollingAxis == Direction.Horizontal ? position.X : position.Y, Time.Current); + Vector2 localPosition = ToLocalSpace(screenSpacePosition); + return TimeAtPosition(scrollingAxis == Direction.Horizontal ? localPosition.X : localPosition.Y, Time.Current); } /// @@ -81,9 +86,8 @@ namespace osu.Game.Rulesets.UI.Scrolling /// public float PositionAtTime(double time, double currentTime) { - float position = scrollingInfo.Algorithm.PositionAt(time, currentTime, timeRange.Value, scrollLength); - flipPositionIfRequired(ref position); - return position; + float scrollPosition = scrollingInfo.Algorithm.PositionAt(time, currentTime, timeRange.Value, scrollLength); + return axisInverted ? scrollLength - scrollPosition : scrollPosition; } /// @@ -97,10 +101,10 @@ namespace osu.Game.Rulesets.UI.Scrolling /// public Vector2 ScreenSpacePositionAtTime(double time) { - float position = PositionAtTime(time, Time.Current); + float localPosition = PositionAtTime(time, Time.Current); return scrollingAxis == Direction.Horizontal - ? ToScreenSpace(new Vector2(position, DrawHeight / 2)) - : ToScreenSpace(new Vector2(DrawWidth / 2, position)); + ? ToScreenSpace(new Vector2(localPosition, DrawHeight / 2)) + : ToScreenSpace(new Vector2(DrawWidth / 2, localPosition)); } /// @@ -113,15 +117,6 @@ namespace osu.Game.Rulesets.UI.Scrolling private float scrollLength => scrollingAxis == Direction.Horizontal ? DrawWidth : DrawHeight; - private void flipPositionIfRequired(ref float position) - { - // We're dealing with coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. - // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position, - // so when scrolling downwards the coordinates need to be flipped. - if (direction.Value == ScrollingDirection.Down || direction.Value == ScrollingDirection.Right) - position = scrollLength - position; - } - protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) { base.AddDrawable(entry, drawable); @@ -237,10 +232,14 @@ namespace osu.Game.Rulesets.UI.Scrolling { float position = PositionAtTime(hitObject.HitObject.StartTime, currentTime); + // The position returned from `PositionAtTime` is assuming the `TopLeft` anchor. + // A correction is needed because the hit objects are using a different anchor for each direction (e.g. `BottomCentre` for `Bottom` direction). + float anchorCorrection = axisInverted ? scrollLength : 0; + if (scrollingAxis == Direction.Horizontal) - hitObject.X = position; + hitObject.X = position - anchorCorrection; else - hitObject.Y = position; + hitObject.Y = position - anchorCorrection; } } } From 564682270a9bb0d49faf095bbad1a919e0915f42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Jun 2021 13:18:52 +0900 Subject: [PATCH 321/520] Revert "Add nested `PlatformActionContainer` to allow testing of platform actions in visual tests" This reverts commit be91203c92ba7004f0f03b32878b3a4182092584. --- osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index c7edc0174a..01dd7a25c8 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -4,7 +4,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Testing.Input; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Sprites; @@ -49,7 +48,7 @@ namespace osu.Game.Tests.Visual InputManager = new ManualInputManager { UseParentInput = true, - Child = new PlatformActionContainer().WithChild(mainContent) + Child = mainContent }, new Container { From 8dd48d48f683823fe511f68fafe29b778a8393fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Jun 2021 14:20:23 +0900 Subject: [PATCH 322/520] Add support for song select leaderboard to handle newly imported scores --- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 +++--- .../Select/Leaderboards/BeatmapLeaderboard.cs | 21 ++++++++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index d18f189a70..c7610e0ba6 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -44,9 +44,9 @@ namespace osu.Game.Online.Leaderboards protected override Container Content => content; - private IEnumerable scores; + private ICollection scores; - public IEnumerable Scores + public ICollection Scores { get => scores; set @@ -290,7 +290,7 @@ namespace osu.Game.Online.Leaderboards getScoresRequest = FetchScores(scores => Schedule(() => { - Scores = scores; + Scores = scores.ToArray(); PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; })); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 8ddae67dba..2bbcb6678f 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -44,6 +44,8 @@ namespace osu.Game.Screens.Select.Leaderboards private IBindable> itemRemoved; + private IBindable> itemAdded; + /// /// Whether to apply the game's currently selected mods as a filter when retrieving scores. /// @@ -85,6 +87,9 @@ namespace osu.Game.Screens.Select.Leaderboards itemRemoved = scoreManager.ItemRemoved.GetBoundCopy(); itemRemoved.BindValueChanged(onScoreRemoved); + + itemAdded = scoreManager.ItemUpdated.GetBoundCopy(); + itemAdded.BindValueChanged(onScoreAdded); } protected override void Reset() @@ -93,7 +98,21 @@ namespace osu.Game.Screens.Select.Leaderboards TopScore = null; } - private void onScoreRemoved(ValueChangedEvent> score) => Schedule(RefreshScores); + private void onScoreRemoved(ValueChangedEvent> score) + { + if (Scope != BeatmapLeaderboardScope.Local) + return; + + Scheduler.AddOnce(RefreshScores); + } + + private void onScoreAdded(ValueChangedEvent> score) + { + if (Scope != BeatmapLeaderboardScope.Local) + return; + + Scheduler.AddOnce(RefreshScores); + } protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; From fc442713bbd46e072c8166db9fd0dc19fdea03c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Jun 2021 14:26:40 +0900 Subject: [PATCH 323/520] Debounce schedule at base class --- osu.Game/Online/Leaderboards/Leaderboard.cs | 10 +++++----- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index c7610e0ba6..70e38e421d 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -126,7 +126,7 @@ namespace osu.Game.Online.Leaderboards return; scope = value; - UpdateScores(); + RefreshScores(); } } @@ -154,7 +154,7 @@ namespace osu.Game.Online.Leaderboards case PlaceholderState.NetworkFailure: replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) { - Action = UpdateScores, + Action = RefreshScores }); break; @@ -254,8 +254,6 @@ namespace osu.Game.Online.Leaderboards apiState.BindValueChanged(onlineStateChanged, true); } - public void RefreshScores() => UpdateScores(); - private APIRequest getScoresRequest; protected abstract bool IsOnlineScope { get; } @@ -267,12 +265,14 @@ namespace osu.Game.Online.Leaderboards case APIState.Online: case APIState.Offline: if (IsOnlineScope) - UpdateScores(); + RefreshScores(); break; } }); + public void RefreshScores() => Scheduler.AddOnce(UpdateScores); + protected void UpdateScores() { // don't display any scores or placeholder until the first Scores_Set has been called. diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 2bbcb6678f..d6967c17a8 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope != BeatmapLeaderboardScope.Local) return; - Scheduler.AddOnce(RefreshScores); + RefreshScores(); } private void onScoreAdded(ValueChangedEvent> score) @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope != BeatmapLeaderboardScope.Local) return; - Scheduler.AddOnce(RefreshScores); + RefreshScores(); } protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; From f8b09b7c81bfa370ed4765133c1231f520b21163 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Jun 2021 14:26:54 +0900 Subject: [PATCH 324/520] Avoid refresh if score is not related to current display --- .../Select/Leaderboards/BeatmapLeaderboard.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index d6967c17a8..587a35c480 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -98,18 +98,22 @@ namespace osu.Game.Screens.Select.Leaderboards TopScore = null; } - private void onScoreRemoved(ValueChangedEvent> score) + private void onScoreRemoved(ValueChangedEvent> score) => + scoreStoreChanged(score); + + private void onScoreAdded(ValueChangedEvent> score) => + scoreStoreChanged(score); + + private void scoreStoreChanged(ValueChangedEvent> score) { if (Scope != BeatmapLeaderboardScope.Local) return; - RefreshScores(); - } - - private void onScoreAdded(ValueChangedEvent> score) - { - if (Scope != BeatmapLeaderboardScope.Local) - return; + if (score.NewValue.TryGetTarget(out var scoreInfo)) + { + if (Beatmap.ID != scoreInfo.BeatmapInfoID) + return; + } RefreshScores(); } From b06477a1f59fbf00546dd84fce6af2b187cf4bb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Jun 2021 14:35:24 +0900 Subject: [PATCH 325/520] Split out tests into individual test methods --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 67cd720260..2a4ad48568 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; @@ -37,18 +38,37 @@ namespace osu.Game.Tests.Visual.SongSelect Size = new Vector2(550f, 450f), Scope = BeatmapLeaderboardScope.Global, }); + } + [Test] + public void TestScoresDisplay() + { AddStep(@"New Scores", newScores); + } + + [Test] + public void TestPersonalBest() + { AddStep(@"Show personal best", showPersonalBest); + AddStep("null personal best position", showPersonalBestWithNullPosition); + } + + [Test] + public void TestPlaceholderStates() + { AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores)); AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure)); AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter)); AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn)); AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable)); AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected)); + } + + [Test] + public void TestBeatmapStates() + { foreach (BeatmapSetOnlineStatus status in Enum.GetValues(typeof(BeatmapSetOnlineStatus))) AddStep($"{status} beatmap", () => showBeatmapWithStatus(status)); - AddStep("null personal best position", showPersonalBestWithNullPosition); } private void showPersonalBestWithNullPosition() From 83402a70db9088e6ddf160b8ceb3de483aedcc28 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Jun 2021 15:06:24 +0900 Subject: [PATCH 326/520] Fix potential null ref when no beatmap is selected --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 587a35c480..a86a614a05 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (score.NewValue.TryGetTarget(out var scoreInfo)) { - if (Beatmap.ID != scoreInfo.BeatmapInfoID) + if (Beatmap?.ID != scoreInfo.BeatmapInfoID) return; } From fcb0b8d825c887cb78937471e8877e2fec86c043 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Jun 2021 15:06:33 +0900 Subject: [PATCH 327/520] Add test coverage --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 110 +++++++++++++++--- 1 file changed, 94 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 2a4ad48568..184a2e59da 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -2,16 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Graphics; +using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Tests.Resources; using osu.Game.Users; using osuTK; @@ -24,26 +30,73 @@ namespace osu.Game.Tests.Visual.SongSelect [Cached] private readonly DialogOverlay dialogOverlay; + private ScoreManager scoreManager; + + private RulesetStore rulesetStore; + private BeatmapManager beatmapManager; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory)); + + return dependencies; + } + public TestSceneBeatmapLeaderboard() { - Add(dialogOverlay = new DialogOverlay + AddRange(new Drawable[] { - Depth = -1 - }); - - Add(leaderboard = new FailableLeaderboard - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(550f, 450f), - Scope = BeatmapLeaderboardScope.Global, + dialogOverlay = new DialogOverlay + { + Depth = -1 + }, + leaderboard = new FailableLeaderboard + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(550f, 450f), + Scope = BeatmapLeaderboardScope.Global, + } }); } [Test] - public void TestScoresDisplay() + public void TestLocalScoresDisplay() { - AddStep(@"New Scores", newScores); + BeatmapInfo beatmapInfo = null; + + AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local); + + AddStep(@"Set beatmap", () => + { + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); + + leaderboard.Beatmap = beatmapInfo; + }); + + clearScores(); + checkCount(0); + + loadMoreScores(() => beatmapInfo); + checkCount(10); + + loadMoreScores(() => beatmapInfo); + checkCount(20); + + clearScores(); + checkCount(0); + } + + [Test] + public void TestGlobalScoresDisplay() + { + AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); + AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(null)); } [Test] @@ -116,9 +169,26 @@ namespace osu.Game.Tests.Visual.SongSelect }; } - private void newScores() + private void loadMoreScores(Func beatmapInfo) { - var scores = new[] + AddStep(@"Load new scores via manager", () => + { + foreach (var score in generateSampleScores(beatmapInfo())) + scoreManager.Import(score).Wait(); + }); + } + + private void clearScores() + { + AddStep("Clear all scores", () => scoreManager.Delete(scoreManager.GetAllUsableScores())); + } + + private void checkCount(int expected) => + AddUntilStep("Correct count displayed", () => leaderboard.ChildrenOfType().Count() == expected); + + private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmap) + { + return new[] { new ScoreInfo { @@ -127,6 +197,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Beatmap = beatmap, User = new User { Id = 6602580, @@ -145,6 +216,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Beatmap = beatmap, User = new User { Id = 4608074, @@ -163,6 +235,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Beatmap = beatmap, User = new User { Id = 1014222, @@ -181,6 +254,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Beatmap = beatmap, User = new User { Id = 1541390, @@ -199,6 +273,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Beatmap = beatmap, User = new User { Id = 2243452, @@ -217,6 +292,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Beatmap = beatmap, User = new User { Id = 2705430, @@ -235,6 +311,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Beatmap = beatmap, User = new User { Id = 7151382, @@ -253,6 +330,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Beatmap = beatmap, User = new User { Id = 2051389, @@ -271,6 +349,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Beatmap = beatmap, User = new User { Id = 6169483, @@ -289,6 +368,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Beatmap = beatmap, User = new User { Id = 6702666, @@ -301,8 +381,6 @@ namespace osu.Game.Tests.Visual.SongSelect }, }, }; - - leaderboard.Scores = scores; } private void showBeatmapWithStatus(BeatmapSetOnlineStatus status) From aa5dae84b2ad3c20580d0a5f7a44aa880c9c1603 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 14 Jun 2021 16:51:17 +0900 Subject: [PATCH 328/520] Make all localisation class strings verbatim --- osu.Game/Localisation/ChatStrings.cs | 6 +++--- osu.Game/Localisation/CommonStrings.cs | 4 ++-- osu.Game/Localisation/Language.cs | 4 ++-- osu.Game/Localisation/NotificationsStrings.cs | 6 +++--- osu.Game/Localisation/NowPlayingStrings.cs | 6 +++--- osu.Game/Localisation/SettingsStrings.cs | 6 +++--- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs index daddb602ad..636351470b 100644 --- a/osu.Game/Localisation/ChatStrings.cs +++ b/osu.Game/Localisation/ChatStrings.cs @@ -7,17 +7,17 @@ namespace osu.Game.Localisation { public static class ChatStrings { - private const string prefix = "osu.Game.Localisation.Chat"; + private const string prefix = @"osu.Game.Localisation.Chat"; /// /// "chat" /// - public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "chat"); + public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"chat"); /// /// "join the real-time discussion" /// - public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "join the real-time discussion"); + public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"join the real-time discussion"); private static string getKey(string key) => $"{prefix}:{key}"; } diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index f448158191..ced0d80955 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -7,12 +7,12 @@ namespace osu.Game.Localisation { public static class CommonStrings { - private const string prefix = "osu.Game.Localisation.Common"; + private const string prefix = @"osu.Game.Localisation.Common"; /// /// "Cancel" /// - public static LocalisableString Cancel => new TranslatableString(getKey("cancel"), "Cancel"); + public static LocalisableString Cancel => new TranslatableString(getKey(@"cancel"), @"Cancel"); private static string getKey(string key) => $"{prefix}:{key}"; } diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index edcf264c7f..a3e845f229 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -7,10 +7,10 @@ namespace osu.Game.Localisation { public enum Language { - [Description("English")] + [Description(@"English")] en, - [Description("日本語")] + [Description(@"日本語")] ja } } diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index 092eec3a6b..ba28ef5560 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -7,17 +7,17 @@ namespace osu.Game.Localisation { public static class NotificationsStrings { - private const string prefix = "osu.Game.Localisation.Notifications"; + private const string prefix = @"osu.Game.Localisation.Notifications"; /// /// "notifications" /// - public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "notifications"); + public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"notifications"); /// /// "waiting for 'ya" /// - public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "waiting for 'ya"); + public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"waiting for 'ya"); private static string getKey(string key) => $"{prefix}:{key}"; } diff --git a/osu.Game/Localisation/NowPlayingStrings.cs b/osu.Game/Localisation/NowPlayingStrings.cs index d742a56895..47646b0f68 100644 --- a/osu.Game/Localisation/NowPlayingStrings.cs +++ b/osu.Game/Localisation/NowPlayingStrings.cs @@ -7,17 +7,17 @@ namespace osu.Game.Localisation { public static class NowPlayingStrings { - private const string prefix = "osu.Game.Localisation.NowPlaying"; + private const string prefix = @"osu.Game.Localisation.NowPlaying"; /// /// "now playing" /// - public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "now playing"); + public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"now playing"); /// /// "manage the currently playing track" /// - public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "manage the currently playing track"); + public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"manage the currently playing track"); private static string getKey(string key) => $"{prefix}:{key}"; } diff --git a/osu.Game/Localisation/SettingsStrings.cs b/osu.Game/Localisation/SettingsStrings.cs index cfbd392691..f4b417fa28 100644 --- a/osu.Game/Localisation/SettingsStrings.cs +++ b/osu.Game/Localisation/SettingsStrings.cs @@ -7,17 +7,17 @@ namespace osu.Game.Localisation { public static class SettingsStrings { - private const string prefix = "osu.Game.Localisation.Settings"; + private const string prefix = @"osu.Game.Localisation.Settings"; /// /// "settings" /// - public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "settings"); + public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"settings"); /// /// "change the way osu! behaves" /// - public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "change the way osu! behaves"); + public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"change the way osu! behaves"); private static string getKey(string key) => $"{prefix}:{key}"; } From b327baa4dea5812234c8ef9754c61a0bfbfcba61 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 14 Jun 2021 17:47:31 +0900 Subject: [PATCH 329/520] Match any arbitrary assembly for localisations --- .../ResourceManagerLocalisationStore.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs index 7b21e1af42..a35ce7a9c8 100644 --- a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs +++ b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Resources; using System.Threading.Tasks; using osu.Framework.Localisation; @@ -34,7 +35,29 @@ namespace osu.Game.Localisation lock (resourceManagers) { if (!resourceManagers.TryGetValue(ns, out var manager)) - resourceManagers[ns] = manager = new ResourceManager(ns, GetType().Assembly); + { + var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + + // Traverse backwards through periods in the namespace to find a matching assembly. + string assemblyName = ns; + + while (!string.IsNullOrEmpty(assemblyName)) + { + var matchingAssembly = loadedAssemblies.FirstOrDefault(asm => asm.GetName().Name == assemblyName); + + if (matchingAssembly != null) + { + resourceManagers[ns] = manager = new ResourceManager(ns, matchingAssembly); + break; + } + + int lastIndex = Math.Max(0, assemblyName.LastIndexOf('.')); + assemblyName = assemblyName.Substring(0, lastIndex); + } + } + + if (manager == null) + return null; try { From 13d0eaa9fe9b7d418d544cf35ea7d0fef55ab04f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Jun 2021 19:03:31 +0900 Subject: [PATCH 330/520] 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 13d45835be..c020b1d783 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7eb3c84582..a7bd5f2e9f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -34,7 +34,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 3e8facaf6e..5b3efb4ba4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From ca061c4b939fdc8b8782ba0e80885f17da3965d5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 14 Jun 2021 19:41:51 +0900 Subject: [PATCH 331/520] Factor out `SkinnableDrawable` component of the catcher to `SkinnableCatcher` --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 37 ++++++------------- .../UI/SkinnableCatcher.cs | 26 +++++++++++++ 2 files changed, 37 insertions(+), 26 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index ee2986c73c..dce89a9dae 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -17,7 +17,6 @@ using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Skinning; -using osu.Game.Rulesets.Catch.Skinning.Default; using osu.Game.Rulesets.Judgements; using osu.Game.Skinning; using osuTK; @@ -83,17 +82,18 @@ namespace osu.Game.Rulesets.Catch.UI /// private readonly Container droppedObjectTarget; - [Cached] - protected readonly Bindable CurrentStateBindable = new Bindable(); - - public CatcherAnimationState CurrentState => CurrentStateBindable.Value; + public CatcherAnimationState CurrentState + { + get => body.AnimationState.Value; + private set => body.AnimationState.Value = value; + } /// /// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable. /// public const float ALLOWED_CATCH_RANGE = 0.8f; - internal Texture CurrentTexture => ((ICatcherSprite)currentCatcher.Drawable).CurrentTexture; + internal Texture CurrentTexture => ((ICatcherSprite)body.Drawable).CurrentTexture; private bool dashing; @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Catch.UI /// private readonly float catchWidth; - private readonly SkinnableDrawable currentCatcher; + private readonly SkinnableCatcher body; private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR; private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR; @@ -161,13 +161,7 @@ namespace osu.Game.Rulesets.Catch.UI Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, }, - currentCatcher = new SkinnableDrawable( - new CatchSkinComponent(CatchSkinComponents.Catcher), - _ => new DefaultCatcher()) - { - Anchor = Anchor.TopCentre, - OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE - }, + body = new SkinnableCatcher(), hitExplosionContainer = new HitExplosionContainer { Anchor = Anchor.TopCentre, @@ -268,17 +262,16 @@ namespace osu.Game.Rulesets.Catch.UI SetHyperDashState(); if (result.IsHit) - updateState(hitObject.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle); + CurrentState = hitObject.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle; else if (!(hitObject is Banana)) - updateState(CatcherAnimationState.Fail); + CurrentState = CatcherAnimationState.Fail; } public void OnRevertResult(DrawableCatchHitObject drawableObject, JudgementResult result) { var catchResult = (CatchJudgementResult)result; - if (CurrentState != catchResult.CatcherAnimationState) - updateState(catchResult.CatcherAnimationState); + CurrentState = catchResult.CatcherAnimationState; if (HyperDashing != catchResult.CatcherHyperDash) { @@ -373,14 +366,6 @@ namespace osu.Game.Rulesets.Catch.UI } } - private void updateState(CatcherAnimationState state) - { - if (CurrentState == state) - return; - - CurrentStateBindable.Value = state; - } - private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Vector2 position) { var caughtObject = getCaughtObject(drawableObject.HitObject); diff --git a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs new file mode 100644 index 0000000000..5bd97858b2 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs @@ -0,0 +1,26 @@ +// 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.Game.Rulesets.Catch.Skinning.Default; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class SkinnableCatcher : SkinnableDrawable + { + [Cached] + public readonly Bindable AnimationState = new Bindable(); + + public SkinnableCatcher() + : base(new CatchSkinComponent(CatchSkinComponents.Catcher), _ => new DefaultCatcher()) + { + Anchor = Anchor.TopCentre; + // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling. + OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE; + } + } +} From 9b6ab4360e497a7b0e32fa1d21bf490d8d70e943 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 14 Jun 2021 19:45:58 +0900 Subject: [PATCH 332/520] Use common skinnable catcher in catcher trails --- .../UI/CatcherTrailDisplay.cs | 4 ++-- .../UI/CatcherTrailSprite.cs | 18 +++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index 0aef215797..dbe41121c8 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -120,8 +120,8 @@ namespace osu.Game.Rulesets.Catch.UI { CatcherTrailSprite sprite = trailPool.Get(); - sprite.Texture = catcher.CurrentTexture; - sprite.Anchor = catcher.Anchor; + sprite.AnimationState = catcher.CurrentState; + sprite.Origin = catcher.Origin; sprite.Scale = catcher.Scale; sprite.Blending = BlendingParameters.Additive; sprite.RelativePositionAxes = catcher.RelativePositionAxes; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs index 0e3e409fac..c4bb0aa1f2 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs @@ -3,32 +3,24 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osuTK; namespace osu.Game.Rulesets.Catch.UI { public class CatcherTrailSprite : PoolableDrawable { - public Texture Texture + public CatcherAnimationState AnimationState { - set => sprite.Texture = value; + set => body.AnimationState.Value = value; } - private readonly Sprite sprite; + private readonly SkinnableCatcher body; public CatcherTrailSprite() { - InternalChild = sprite = new Sprite - { - RelativeSizeAxes = Axes.Both - }; - Size = new Vector2(CatcherArea.CATCHER_SIZE); - - // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling. - OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE; + Origin = Anchor.TopCentre; + InternalChild = body = new SkinnableCatcher(); } protected override void FreeAfterUse() From c094914023aab5e29e71cba493a5858233fbd8cc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 14 Jun 2021 19:46:48 +0900 Subject: [PATCH 333/520] Simplify catcher trail creation --- osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs | 3 --- osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index dbe41121c8..382e796480 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -121,10 +121,7 @@ namespace osu.Game.Rulesets.Catch.UI CatcherTrailSprite sprite = trailPool.Get(); sprite.AnimationState = catcher.CurrentState; - sprite.Origin = catcher.Origin; sprite.Scale = catcher.Scale; - sprite.Blending = BlendingParameters.Additive; - sprite.RelativePositionAxes = catcher.RelativePositionAxes; sprite.Position = catcher.Position; target.Add(sprite); diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs index c4bb0aa1f2..8417e5a250 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Catch.UI { Size = new Vector2(CatcherArea.CATCHER_SIZE); Origin = Anchor.TopCentre; + Blending = BlendingParameters.Additive; InternalChild = body = new SkinnableCatcher(); } From 38a56d64d316404ac0ca2260ca1ad2efc46a5b94 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 14 Jun 2021 19:47:18 +0900 Subject: [PATCH 334/520] Rename `CatcherTrailSprite` -> `CatcherTrail` --- ...{CatcherTrailSprite.cs => CatcherTrail.cs} | 4 ++-- .../UI/CatcherTrailDisplay.cs | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) rename osu.Game.Rulesets.Catch/UI/{CatcherTrailSprite.cs => CatcherTrail.cs} (90%) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs similarity index 90% rename from osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs rename to osu.Game.Rulesets.Catch/UI/CatcherTrail.cs index 8417e5a250..90fb59db9a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.UI { - public class CatcherTrailSprite : PoolableDrawable + public class CatcherTrail : PoolableDrawable { public CatcherAnimationState AnimationState { @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly SkinnableCatcher body; - public CatcherTrailSprite() + public CatcherTrail() { Size = new Vector2(CatcherArea.CATCHER_SIZE); Origin = Anchor.TopCentre; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index 382e796480..7e4a5b6a86 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -19,11 +19,11 @@ namespace osu.Game.Rulesets.Catch.UI { private readonly Catcher catcher; - private readonly DrawablePool trailPool; + private readonly DrawablePool trailPool; - private readonly Container dashTrails; - private readonly Container hyperDashTrails; - private readonly Container endGlowSprites; + private readonly Container dashTrails; + private readonly Container hyperDashTrails; + private readonly Container endGlowSprites; private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR; @@ -83,10 +83,10 @@ namespace osu.Game.Rulesets.Catch.UI InternalChildren = new Drawable[] { - trailPool = new DrawablePool(30), - dashTrails = new Container { RelativeSizeAxes = Axes.Both }, - hyperDashTrails = new Container { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR }, - endGlowSprites = new Container { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR }, + trailPool = new DrawablePool(30), + dashTrails = new Container { RelativeSizeAxes = Axes.Both }, + hyperDashTrails = new Container { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR }, + endGlowSprites = new Container { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR }, }; } @@ -116,9 +116,9 @@ namespace osu.Game.Rulesets.Catch.UI Scheduler.AddDelayed(displayTrail, catcher.HyperDashing ? 25 : 50); } - private CatcherTrailSprite createTrailSprite(Container target) + private CatcherTrail createTrailSprite(Container target) { - CatcherTrailSprite sprite = trailPool.Get(); + CatcherTrail sprite = trailPool.Get(); sprite.AnimationState = catcher.CurrentState; sprite.Scale = catcher.Scale; From df16d4baccff180aadb9a441d9315f11f1e67811 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 14 Jun 2021 20:26:33 +0900 Subject: [PATCH 335/520] Remove `CurrentTexture` from catcher --- .../Skinning/Default/DefaultCatcher.cs | 4 +--- osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs | 12 ------------ .../Skinning/Legacy/LegacyCatcherNew.cs | 6 +----- .../Skinning/Legacy/LegacyCatcherOld.cs | 7 +------ osu.Game.Rulesets.Catch/UI/Catcher.cs | 3 --- 5 files changed, 3 insertions(+), 29 deletions(-) delete mode 100644 osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs index 364fc211a0..e423f21b98 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs @@ -12,12 +12,10 @@ using osu.Game.Rulesets.Catch.UI; namespace osu.Game.Rulesets.Catch.Skinning.Default { - public class DefaultCatcher : CompositeDrawable, ICatcherSprite + public class DefaultCatcher : CompositeDrawable { public Bindable CurrentState { get; } = new Bindable(); - public Texture CurrentTexture => sprite.Texture; - private readonly Sprite sprite; private readonly Dictionary textures = new Dictionary(); diff --git a/osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs b/osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs deleted file mode 100644 index 073868e947..0000000000 --- a/osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs +++ /dev/null @@ -1,12 +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 osu.Framework.Graphics.Textures; - -namespace osu.Game.Rulesets.Catch.Skinning -{ - public interface ICatcherSprite - { - Texture CurrentTexture { get; } - } -} diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs index 2bf8b28aa2..9df87c92ea 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs @@ -9,21 +9,17 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Game.Rulesets.Catch.UI; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Catch.Skinning.Legacy { - public class LegacyCatcherNew : CompositeDrawable, ICatcherSprite + public class LegacyCatcherNew : CompositeDrawable { [Resolved] private Bindable currentState { get; set; } - public Texture CurrentTexture => (currentDrawable as TextureAnimation)?.CurrentFrame ?? (currentDrawable as Sprite)?.Texture; - private readonly Dictionary drawables = new Dictionary(); private Drawable currentDrawable; diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs index a8948d2ed0..3e679171b2 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs @@ -3,19 +3,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Catch.Skinning.Legacy { - public class LegacyCatcherOld : CompositeDrawable, ICatcherSprite + public class LegacyCatcherOld : CompositeDrawable { - public Texture CurrentTexture => (InternalChild as TextureAnimation)?.CurrentFrame ?? (InternalChild as Sprite)?.Texture; - public LegacyCatcherOld() { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index dce89a9dae..1f01dbabb5 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; -using osu.Framework.Graphics.Textures; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -93,8 +92,6 @@ namespace osu.Game.Rulesets.Catch.UI /// public const float ALLOWED_CATCH_RANGE = 0.8f; - internal Texture CurrentTexture => ((ICatcherSprite)body.Drawable).CurrentTexture; - private bool dashing; public bool Dashing From cb1e2e3d9785748718a37c43f4b01c8e9c704342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Jun 2021 21:51:32 +0200 Subject: [PATCH 336/520] Improve xmldoc --- .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index d75954d77f..94cc7ed095 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -25,8 +25,12 @@ namespace osu.Game.Rulesets.UI.Scrolling private Direction scrollingAxis => direction.Value == ScrollingDirection.Left || direction.Value == ScrollingDirection.Right ? Direction.Horizontal : Direction.Vertical; /// - /// Whether the scrolling direction is the positive-to-negative direction in the local coordinate. + /// The scrolling axis is inverted if objects temporally farther in the future have a smaller position value across the scrolling axis. /// + /// + /// is inverted, because given two objects, one of which is at the current time and one of which is 1000ms in the future, + /// in the current time instant the future object is spatially above the current object, and therefore has a smaller value of the Y coordinate of its position. + /// private bool axisInverted => direction.Value == ScrollingDirection.Down || direction.Value == ScrollingDirection.Right; /// @@ -58,7 +62,7 @@ namespace osu.Game.Rulesets.UI.Scrolling } /// - /// Given a position at , return the time of the object corresponding the position. + /// Given a position at , return the time of the object corresponding to the position. /// /// /// If there are multiple valid time values, one arbitrary time is returned. From 9d9c5902bbe616ffcfdc53b116f907cdb3644f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Jun 2021 22:46:56 +0200 Subject: [PATCH 337/520] Temporarily disable `dotnet format` to unblock CI --- appveyor.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index a4a0cedc66..845751ef07 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,7 +17,11 @@ build: publish_nuget: true after_build: - ps: dotnet tool restore - - ps: dotnet format --dry-run --check + + # Temporarily disabled until the tool is upgraded to 5.0. + # The version specified in .config/dotnet-tools.json (3.1.37601) won't run on .NET hosts >=5.0.7. + # - ps: dotnet format --dry-run --check + - ps: .\InspectCode.ps1 test: assemblies: From f6c6eea6dce32a50514fb3713f4ef301c0f1c1ba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Jun 2021 11:14:35 +0900 Subject: [PATCH 338/520] Make PresentScore() only consider replay hash --- osu.Game/OsuGame.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 019d3b3cd0..1466d685d6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -426,9 +426,8 @@ namespace osu.Game { // The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database // to ensure all the required data for presenting a replay are present. - var databasedScoreInfo = score.OnlineScoreID != null - ? ScoreManager.Query(s => s.OnlineScoreID == score.OnlineScoreID) - : ScoreManager.Query(s => s.Hash == score.Hash); + // Todo: This should use the OnlineScoreID if available, however lazer scores are imported without an OnlineScoreID for the time being (see Player.ImportScore()). + var databasedScoreInfo = ScoreManager.Query(s => s.Hash == score.Hash); if (databasedScoreInfo == null) { From bbf00226898dd7c374a17712866f3d8fa40266f1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 15 Jun 2021 13:11:07 +0900 Subject: [PATCH 339/520] Use natural anchor for `TimeAtPosition` and `PositionAtTime` The natural anchor is the end of the scrolling direction (e.g. Bottom for Down scrolling). --- .../Scrolling/ScrollingHitObjectContainer.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 94cc7ed095..3b15bc2cdf 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.UI.Scrolling /// public double TimeAtPosition(float localPosition, double currentTime) { - float scrollPosition = axisInverted ? scrollLength - localPosition : localPosition; + float scrollPosition = axisInverted ? -localPosition : localPosition; return scrollingInfo.Algorithm.TimeAt(scrollPosition, currentTime, timeRange.Value, scrollLength); } @@ -81,8 +81,10 @@ namespace osu.Game.Rulesets.UI.Scrolling /// public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) { - Vector2 localPosition = ToLocalSpace(screenSpacePosition); - return TimeAtPosition(scrollingAxis == Direction.Horizontal ? localPosition.X : localPosition.Y, Time.Current); + Vector2 pos = ToLocalSpace(screenSpacePosition); + float localPosition = scrollingAxis == Direction.Horizontal ? pos.X : pos.Y; + localPosition -= axisInverted ? scrollLength : 0; + return TimeAtPosition(localPosition, Time.Current); } /// @@ -91,7 +93,7 @@ namespace osu.Game.Rulesets.UI.Scrolling public float PositionAtTime(double time, double currentTime) { float scrollPosition = scrollingInfo.Algorithm.PositionAt(time, currentTime, timeRange.Value, scrollLength); - return axisInverted ? scrollLength - scrollPosition : scrollPosition; + return axisInverted ? -scrollPosition : scrollPosition; } /// @@ -106,6 +108,7 @@ namespace osu.Game.Rulesets.UI.Scrolling public Vector2 ScreenSpacePositionAtTime(double time) { float localPosition = PositionAtTime(time, Time.Current); + localPosition += axisInverted ? scrollLength : 0; return scrollingAxis == Direction.Horizontal ? ToScreenSpace(new Vector2(localPosition, DrawHeight / 2)) : ToScreenSpace(new Vector2(DrawWidth / 2, localPosition)); @@ -236,14 +239,10 @@ namespace osu.Game.Rulesets.UI.Scrolling { float position = PositionAtTime(hitObject.HitObject.StartTime, currentTime); - // The position returned from `PositionAtTime` is assuming the `TopLeft` anchor. - // A correction is needed because the hit objects are using a different anchor for each direction (e.g. `BottomCentre` for `Bottom` direction). - float anchorCorrection = axisInverted ? scrollLength : 0; - if (scrollingAxis == Direction.Horizontal) - hitObject.X = position - anchorCorrection; + hitObject.X = position; else - hitObject.Y = position - anchorCorrection; + hitObject.Y = position; } } } From d0e57f7dd943f8266ddd6267e009bf5424bcc621 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 15 Jun 2021 13:20:33 +0900 Subject: [PATCH 340/520] Use `HitObject` instead of DHO for mania selection blueprint layout - Fix moving selected hold note between columns will cause a crash --- .../Editor/TestSceneManiaHitObjectComposer.cs | 6 +-- .../Edit/Blueprints/HoldNoteNoteOverlay.cs | 43 ------------------- .../Edit/Blueprints/HoldNotePosition.cs | 11 ----- .../Blueprints/HoldNoteSelectionBlueprint.cs | 32 +++++--------- .../Blueprints/ManiaSelectionBlueprint.cs | 26 +++++------ .../Edit/Blueprints/NoteSelectionBlueprint.cs | 9 ---- 6 files changed, 24 insertions(+), 103 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteOverlay.cs delete mode 100644 osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePosition.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index 8474279b01..01d80881fa 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -12,7 +12,7 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit; -using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Skinning.Default; @@ -184,8 +184,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft)); AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft)); - AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition); - AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition); + AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition); + AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition); } private void setScrollStep(ScrollingDirection direction) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteOverlay.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteOverlay.cs deleted file mode 100644 index 6933571be8..0000000000 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteOverlay.cs +++ /dev/null @@ -1,43 +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 osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; -using osu.Game.Rulesets.Mania.Objects.Drawables; - -namespace osu.Game.Rulesets.Mania.Edit.Blueprints -{ - public class HoldNoteNoteOverlay : CompositeDrawable - { - private readonly HoldNoteSelectionBlueprint holdNoteBlueprint; - private readonly HoldNotePosition position; - - public HoldNoteNoteOverlay(HoldNoteSelectionBlueprint holdNoteBlueprint, HoldNotePosition position) - { - this.holdNoteBlueprint = holdNoteBlueprint; - this.position = position; - - InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X }; - } - - protected override void Update() - { - base.Update(); - - var drawableObject = holdNoteBlueprint.DrawableObject; - - // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly. - if (drawableObject.IsLoaded) - { - DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)drawableObject.Head : drawableObject.Tail; - - Anchor = note.Anchor; - Origin = note.Origin; - - Size = note.DrawSize; - Position = note.DrawPosition; - } - } - } -} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePosition.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePosition.cs deleted file mode 100644 index 219dad566d..0000000000 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePosition.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Rulesets.Mania.Edit.Blueprints -{ - public enum HoldNotePosition - { - Start, - End - } -} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index d04c5cd4aa..5259fcbd5f 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -2,14 +2,13 @@ // 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.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -17,13 +16,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint { - public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; - - private readonly IBindable direction = new Bindable(); - [Resolved] private OsuColour colours { get; set; } + private EditNotePiece head; + private EditNotePiece tail; + public HoldNoteSelectionBlueprint(HoldNote hold) : base(hold) { @@ -32,12 +30,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [BackgroundDependencyLoader] private void load(IScrollingInfo scrollingInfo) { - direction.BindTo(scrollingInfo.Direction); - InternalChildren = new Drawable[] { - new HoldNoteNoteOverlay(this, HoldNotePosition.Start), - new HoldNoteNoteOverlay(this, HoldNotePosition.End), + head = new EditNotePiece { RelativeSizeAxes = Axes.X }, + tail = new EditNotePiece { RelativeSizeAxes = Axes.X }, new Container { RelativeSizeAxes = Axes.Both, @@ -58,21 +54,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.Update(); - // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly. - if (DrawableObject.IsLoaded) - { - Size = DrawableObject.DrawSize + new Vector2(0, DrawableObject.Tail.DrawHeight); - - // This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do - // When scrolling upwards our origin is already at the top of the head note (which is the intended location), - // but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note) - if (direction.Value == ScrollingDirection.Down) - Y -= DrawableObject.Tail.DrawHeight; - } + head.Y = HitObjectContainer.PositionAtTime(HitObject.Head.StartTime, HitObject.StartTime); + tail.Y = HitObjectContainer.PositionAtTime(HitObject.Tail.StartTime, HitObject.StartTime); + Height = HitObjectContainer.LengthAtTime(HitObject.StartTime, HitObject.EndTime) + tail.DrawHeight; } public override Quad SelectionQuad => ScreenSpaceDrawQuad; - public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre; + public override Vector2 ScreenSpaceSelectionPoint => head.ScreenSpaceDrawQuad.Centre; } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index e744bd3c83..bd1e5c22b3 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -5,20 +5,22 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; -using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public abstract class ManiaSelectionBlueprint : HitObjectSelectionBlueprint where T : ManiaHitObject { - public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject; + [Resolved] + private HitObjectComposer composer { get; set; } [Resolved] private IScrollingInfo scrollingInfo { get; set; } + protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)composer.Playfield).GetColumn(HitObject.Column).HitObjectContainer; + protected ManiaSelectionBlueprint(T hitObject) : base(hitObject) { @@ -29,19 +31,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.Update(); - Position = Parent.ToLocalSpace(DrawableObject.ToScreenSpace(Vector2.Zero)); - } + var anchor = scrollingInfo.Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; + Anchor = Origin = anchor; + foreach (var child in InternalChildren) + child.Anchor = child.Origin = anchor; - public override void Show() - { - DrawableObject.AlwaysAlive = true; - base.Show(); - } - - public override void Hide() - { - DrawableObject.AlwaysAlive = false; - base.Hide(); + Position = Parent.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition; + Width = HitObjectContainer.DrawWidth; } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs index e2b6ee0048..e7a03905d2 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -14,14 +14,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X }); } - - protected override void Update() - { - base.Update(); - - // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly. - if (DrawableObject.IsLoaded) - Size = DrawableObject.DrawSize; - } } } From eb4c093371267c6446d6d37ed8a51cfb42690db6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Jun 2021 14:06:17 +0900 Subject: [PATCH 341/520] Use hash as fallback --- osu.Game/OsuGame.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1466d685d6..b226932555 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -427,7 +427,12 @@ namespace osu.Game // The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database // to ensure all the required data for presenting a replay are present. // Todo: This should use the OnlineScoreID if available, however lazer scores are imported without an OnlineScoreID for the time being (see Player.ImportScore()). - var databasedScoreInfo = ScoreManager.Query(s => s.Hash == score.Hash); + ScoreInfo databasedScoreInfo = null; + + if (score.OnlineScoreID != null) + databasedScoreInfo = ScoreManager.Query(s => s.OnlineScoreID == score.OnlineScoreID); + + databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == score.Hash); if (databasedScoreInfo == null) { From 579a4aa9c8032da0c18fda6f7de4ea34abd33157 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Jun 2021 14:10:09 +0900 Subject: [PATCH 342/520] Remove comment --- osu.Game/OsuGame.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b226932555..da104852e3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -426,7 +426,6 @@ namespace osu.Game { // The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database // to ensure all the required data for presenting a replay are present. - // Todo: This should use the OnlineScoreID if available, however lazer scores are imported without an OnlineScoreID for the time being (see Player.ImportScore()). ScoreInfo databasedScoreInfo = null; if (score.OnlineScoreID != null) From ef96ceb4abc61821768585e269ce2fc81582c43b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 15 Jun 2021 14:43:04 +0900 Subject: [PATCH 343/520] Introduce `IPlayfieldProvider` --- .../Edit/Blueprints/ManiaSelectionBlueprint.cs | 4 ++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 ++- osu.Game/Rulesets/Edit/IPlayfieldProvider.cs | 12 ++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/IPlayfieldProvider.cs diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index bd1e5c22b3..1b5cb03204 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -14,12 +14,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints where T : ManiaHitObject { [Resolved] - private HitObjectComposer composer { get; set; } + private IPlayfieldProvider playfieldProvider { get; set; } [Resolved] private IScrollingInfo scrollingInfo { get; set; } - protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)composer.Playfield).GetColumn(HitObject.Column).HitObjectContainer; + protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfieldProvider.Playfield).GetColumn(HitObject.Column).HitObjectContainer; protected ManiaSelectionBlueprint(T hitObject) : base(hitObject) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b47cf97a4d..67c18b7e3c 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -415,7 +415,8 @@ namespace osu.Game.Rulesets.Edit /// [Cached(typeof(HitObjectComposer))] [Cached(typeof(IPositionSnapProvider))] - public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider + [Cached(typeof(IPlayfieldProvider))] + public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider, IPlayfieldProvider { protected HitObjectComposer() { diff --git a/osu.Game/Rulesets/Edit/IPlayfieldProvider.cs b/osu.Game/Rulesets/Edit/IPlayfieldProvider.cs new file mode 100644 index 0000000000..4bfd4d2728 --- /dev/null +++ b/osu.Game/Rulesets/Edit/IPlayfieldProvider.cs @@ -0,0 +1,12 @@ +// 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.Rulesets.UI; + +namespace osu.Game.Rulesets.Edit +{ + public interface IPlayfieldProvider + { + Playfield Playfield { get; } + } +} From 403aa433cfcd5f49f20873d68df46f2eaf6d825d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 15 Jun 2021 15:14:14 +0900 Subject: [PATCH 344/520] Rewrite mania selection blueprint test scene --- .../ManiaSelectionBlueprintTestScene.cs | 48 ++++++++++++++----- .../TestSceneHoldNoteSelectionBlueprint.cs | 40 ++-------------- .../Editor/TestSceneNoteSelectionBlueprint.cs | 22 ++------- 3 files changed, 42 insertions(+), 68 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 176fbba921..36749fad1c 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -1,31 +1,53 @@ // 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 osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Timing; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests.Editor { - public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene + [Cached(typeof(IPlayfieldProvider))] + public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene, IPlayfieldProvider { - [Cached(Type = typeof(IAdjustableClock))] - private readonly IAdjustableClock clock = new StopwatchClock(); + protected override Container Content => blueprints ?? base.Content; + + private readonly Container blueprints; + + public Playfield Playfield { get; } + + private readonly ScrollingTestContainer scrollingTestContainer; + + protected ScrollingDirection Direction + { + set => scrollingTestContainer.Direction = value; + } protected ManiaSelectionBlueprintTestScene() { - Add(new Column(0) + var stageDefinitions = new List { new StageDefinition { Columns = 1 } }; + base.Content.Child = scrollingTestContainer = new ScrollingTestContainer(ScrollingDirection.Down) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AccentColour = Color4.OrangeRed, - Clock = new FramedClock(new StopwatchClock()), // No scroll - }); + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + Playfield = new ManiaPlayfield(stageDefinitions) + { + RelativeSizeAxes = Axes.Both, + }, + blueprints = new Container + { + RelativeSizeAxes = Axes.Both + } + } + }; } - - public ManiaPlayfield Playfield => null; } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs index 5e99264d7d..3cf9c6ad65 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs @@ -1,56 +1,24 @@ // 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.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.UI.Scrolling; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene { - private readonly DrawableHoldNote drawableObject; - - protected override Container Content => content ?? base.Content; - private readonly Container content; - public TestSceneHoldNoteSelectionBlueprint() { - var holdNote = new HoldNote { Column = 0, Duration = 1000 }; + var holdNote = new HoldNote { Column = 0, Duration = 500 }; holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Y, - Width = 50, - Child = drawableObject = new DrawableHoldNote(holdNote) - { - Height = 300, - AccentColour = { Value = OsuColour.Gray(0.3f) } - } - }; - - AddBlueprint(new HoldNoteSelectionBlueprint(holdNote), drawableObject); - } - - protected override void Update() - { - base.Update(); - - foreach (var nested in drawableObject.NestedHitObjects) - { - double finalPosition = (nested.HitObject.StartTime - drawableObject.HitObject.StartTime) / drawableObject.HitObject.Duration; - nested.Y = (float)(-finalPosition * content.DrawHeight); - } + var drawableHitObject = new DrawableHoldNote(holdNote); + Playfield.Add(drawableHitObject); + AddBlueprint(new HoldNoteSelectionBlueprint(holdNote), drawableHitObject); } } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs index 9c3ad0b4ff..dc33e30de7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs @@ -1,40 +1,24 @@ // 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.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.UI.Scrolling; -using osu.Game.Tests.Visual; -using osuTK; namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene { - protected override Container Content => content ?? base.Content; - private readonly Container content; - public TestSceneNoteSelectionBlueprint() { var note = new Note { Column = 0 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - DrawableNote drawableObject; - - base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(50, 20), - Child = drawableObject = new DrawableNote(note) - }; - - AddBlueprint(new NoteSelectionBlueprint(note), drawableObject); + var drawableHitObject = new DrawableNote(note); + Playfield.Add(drawableHitObject); + AddBlueprint(new NoteSelectionBlueprint(note), drawableHitObject); } } } From a431b4eeda6531401581c459a133671a51621555 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 15 Jun 2021 15:22:36 +0900 Subject: [PATCH 345/520] Add scrolling direction toggle for mania selection blueprint test scene --- .../ManiaSelectionBlueprintTestScene.cs | 8 +++++--- .../TestSceneHoldNoteSelectionBlueprint.cs | 19 ++++++++++++++----- .../Editor/TestSceneNoteSelectionBlueprint.cs | 18 +++++++++++++----- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 36749fad1c..e5abbc7246 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -30,10 +30,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor set => scrollingTestContainer.Direction = value; } - protected ManiaSelectionBlueprintTestScene() + protected ManiaSelectionBlueprintTestScene(int columns) { - var stageDefinitions = new List { new StageDefinition { Columns = 1 } }; - base.Content.Child = scrollingTestContainer = new ScrollingTestContainer(ScrollingDirection.Down) + var stageDefinitions = new List { new StageDefinition { Columns = columns } }; + base.Content.Child = scrollingTestContainer = new ScrollingTestContainer(ScrollingDirection.Up) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -48,6 +48,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor } } }; + + AddToggleStep("Downward scroll", b => Direction = b ? ScrollingDirection.Down : ScrollingDirection.Up); } } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs index 3cf9c6ad65..9953b8e3c0 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs @@ -12,13 +12,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public class TestSceneHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene { public TestSceneHoldNoteSelectionBlueprint() + : base(4) { - var holdNote = new HoldNote { Column = 0, Duration = 500 }; - holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + for (int i = 0; i < 4; i++) + { + var holdNote = new HoldNote + { + Column = i, + StartTime = i * 100, + Duration = 500 + }; + holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - var drawableHitObject = new DrawableHoldNote(holdNote); - Playfield.Add(drawableHitObject); - AddBlueprint(new HoldNoteSelectionBlueprint(holdNote), drawableHitObject); + var drawableHitObject = new DrawableHoldNote(holdNote); + Playfield.Add(drawableHitObject); + AddBlueprint(new HoldNoteSelectionBlueprint(holdNote), drawableHitObject); + } } } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs index dc33e30de7..3586eecc44 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs @@ -12,13 +12,21 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene { public TestSceneNoteSelectionBlueprint() + : base(4) { - var note = new Note { Column = 0 }; - note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + for (int i = 0; i < 4; i++) + { + var note = new Note + { + Column = i, + StartTime = i * 200, + }; + note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - var drawableHitObject = new DrawableNote(note); - Playfield.Add(drawableHitObject); - AddBlueprint(new NoteSelectionBlueprint(note), drawableHitObject); + var drawableHitObject = new DrawableNote(note); + Playfield.Add(drawableHitObject); + AddBlueprint(new NoteSelectionBlueprint(note), drawableHitObject); + } } } } From f39dbb8b6e3dcdbc838d42a9a7a9a69bf1b0fe05 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Jun 2021 21:42:09 +0900 Subject: [PATCH 346/520] Upgrade cfs --- .config/dotnet-tools.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 84673fe2ba..e72bed602e 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -27,7 +27,7 @@ ] }, "codefilesanity": { - "version": "15.0.0", + "version": "0.0.36", "commands": [ "CodeFileSanity" ] @@ -39,4 +39,4 @@ ] } } -} +} \ No newline at end of file From e29e2328f89d14f79bcf1d09fce5b7126098b90a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Jun 2021 21:45:02 +0900 Subject: [PATCH 347/520] Inline inspection actions into appveyor.yml --- appveyor.yml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 845751ef07..accc913bf5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,28 +1,35 @@ clone_depth: 1 version: '{branch}-{build}' image: Visual Studio 2019 +cache: + - '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml' + dotnet_csproj: patch: true file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects version: '0.0.{build}' -cache: - - '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml' + before_build: - - ps: dotnet --info # Useful when version mismatch between CI and local - - ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects + - cmd: dotnet --info # Useful when version mismatch between CI and local + - cmd: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects + build: project: osu.sln parallel: true verbosity: minimal publish_nuget: true + after_build: - - ps: dotnet tool restore + - cmd: dotnet tool restore # Temporarily disabled until the tool is upgraded to 5.0. # The version specified in .config/dotnet-tools.json (3.1.37601) won't run on .NET hosts >=5.0.7. - # - ps: dotnet format --dry-run --check + # - cmd: dotnet format --dry-run --check + + - cmd: dotnet CodeFileSanity + - cmd: dotnet jb inspectcode "osu.Desktop.slnf" --output="temp/inspectcodereport.xml" --caches-home="temp/inspectcode" --verbosity=WARN + - cmd: dotnet nvika parsereport "temp/inspectcodereport.xml" --treatwarningsaserrors - - ps: .\InspectCode.ps1 test: assemblies: except: From 9fcf10536435688241f783ef2f39160e0141f529 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Jun 2021 21:46:23 +0900 Subject: [PATCH 348/520] Remove cake --- .config/dotnet-tools.json | 6 ------ .vscode/launch.json | 14 ------------- InspectCode.ps1 | 27 -------------------------- build/Desktop.proj | 17 ---------------- build/InspectCode.cake | 41 --------------------------------------- cake.config | 5 ----- 6 files changed, 110 deletions(-) delete mode 100644 InspectCode.ps1 delete mode 100644 build/Desktop.proj delete mode 100644 build/InspectCode.cake delete mode 100644 cake.config diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index e72bed602e..1dca8b3859 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -2,12 +2,6 @@ "version": 1, "isRoot": true, "tools": { - "cake.tool": { - "version": "0.35.0", - "commands": [ - "dotnet-cake" - ] - }, "dotnet-format": { "version": "3.1.37601", "commands": [ diff --git a/.vscode/launch.json b/.vscode/launch.json index afd997f91d..1b590008cd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -113,20 +113,6 @@ "cwd": "${workspaceRoot}", "preLaunchTask": "Build benchmarks", "console": "internalConsole" - }, - { - "name": "Cake: Debug Script", - "type": "coreclr", - "request": "launch", - "program": "${workspaceRoot}/build/tools/Cake.CoreCLR/0.30.0/Cake.dll", - "args": [ - "${workspaceRoot}/build/build.cake", - "--debug", - "--verbosity=diagnostic" - ], - "cwd": "${workspaceRoot}/build", - "stopAtEntry": true, - "externalConsole": false } ] } diff --git a/InspectCode.ps1 b/InspectCode.ps1 deleted file mode 100644 index 6ed935fdbb..0000000000 --- a/InspectCode.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -[CmdletBinding()] -Param( - [string]$Target, - [string]$Configuration, - [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] - [string]$Verbosity, - [switch]$ShowDescription, - [Alias("WhatIf", "Noop")] - [switch]$DryRun, - [Parameter(Position = 0, Mandatory = $false, ValueFromRemainingArguments = $true)] - [string[]]$ScriptArgs -) - -# Build Cake arguments -$cakeArguments = ""; -if ($Target) { $cakeArguments += "-target=$Target" } -if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } -if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } -if ($ShowDescription) { $cakeArguments += "-showdescription" } -if ($DryRun) { $cakeArguments += "-dryrun" } -if ($Experimental) { $cakeArguments += "-experimental" } -$cakeArguments += $ScriptArgs - -dotnet tool restore -dotnet cake ./build/InspectCode.cake --bootstrap -dotnet cake ./build/InspectCode.cake $cakeArguments -exit $LASTEXITCODE \ No newline at end of file diff --git a/build/Desktop.proj b/build/Desktop.proj deleted file mode 100644 index b1c6b065e8..0000000000 --- a/build/Desktop.proj +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/InspectCode.cake b/build/InspectCode.cake deleted file mode 100644 index 6836d9071b..0000000000 --- a/build/InspectCode.cake +++ /dev/null @@ -1,41 +0,0 @@ -#addin "nuget:?package=CodeFileSanity&version=0.0.36" - -/////////////////////////////////////////////////////////////////////////////// -// ARGUMENTS -/////////////////////////////////////////////////////////////////////////////// - -var target = Argument("target", "CodeAnalysis"); -var configuration = Argument("configuration", "Release"); - -var rootDirectory = new DirectoryPath(".."); -var sln = rootDirectory.CombineWithFilePath("osu.sln"); -var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf"); - -/////////////////////////////////////////////////////////////////////////////// -// TASKS -/////////////////////////////////////////////////////////////////////////////// - -Task("InspectCode") - .Does(() => { - var inspectcodereport = "inspectcodereport.xml"; - var cacheDir = "inspectcode"; - var verbosity = AppVeyor.IsRunningOnAppVeyor ? "WARN" : "INFO"; // Don't flood CI output - - DotNetCoreTool(rootDirectory.FullPath, - "jb", $@"inspectcode ""{desktopSlnf}"" --output=""{inspectcodereport}"" --caches-home=""{cacheDir}"" --verbosity={verbosity}"); - DotNetCoreTool(rootDirectory.FullPath, "nvika", $@"parsereport ""{inspectcodereport}"" --treatwarningsaserrors"); - }); - -Task("CodeFileSanity") - .Does(() => { - ValidateCodeSanity(new ValidateCodeSanitySettings { - RootDirectory = rootDirectory.FullPath, - IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor - }); - }); - -Task("CodeAnalysis") - .IsDependentOn("CodeFileSanity") - .IsDependentOn("InspectCode"); - -RunTarget(target); \ No newline at end of file diff --git a/cake.config b/cake.config deleted file mode 100644 index 187d825591..0000000000 --- a/cake.config +++ /dev/null @@ -1,5 +0,0 @@ - -[Nuget] -Source=https://api.nuget.org/v3/index.json -UseInProcessClient=true -LoadDependencies=true From 04e8703eeea79e71790a4f0efc8c1ece02a788f0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Jun 2021 21:59:18 +0900 Subject: [PATCH 349/520] Add github actions workflows --- .config/.github/workflows/ci.yml | 95 ++++++++++++++++++++++ .config/.github/workflows/report-nunit.yml | 31 +++++++ 2 files changed, 126 insertions(+) create mode 100644 .config/.github/workflows/ci.yml create mode 100644 .config/.github/workflows/report-nunit.yml diff --git a/.config/.github/workflows/ci.yml b/.config/.github/workflows/ci.yml new file mode 100644 index 0000000000..0be3f64ab3 --- /dev/null +++ b/.config/.github/workflows/ci.yml @@ -0,0 +1,95 @@ +on: [push, pull_request] +name: Continuous Integration + +jobs: + test: + name: Test + runs-on: ${{matrix.os.fullname}} + env: + OSU_EXECUTION_MODE: ${{matrix.threadingMode}} + strategy: + fail-fast: false + matrix: + os: + - { prettyname: Windows, fullname: windows-latest } + - { prettyname: macOS, fullname: macos-latest } + - { prettyname: Linux, fullname: ubuntu-latest } + threadingMode: ['SingleThread', 'MultiThreaded'] + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install .NET 5.0.x + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "5.0.x" + + # FIXME: libavformat is not included in Ubuntu. Let's fix that. + # https://github.com/ppy/osu-framework/issues/4349 + # Remove this once https://github.com/actions/virtual-environments/issues/3306 has been resolved. + - name: Install libavformat-dev + if: ${{matrix.os.fullname == 'ubuntu-latest'}} + run: | + sudo apt-get update && \ + sudo apt-get -y install libavformat-dev + + - name: Compile + run: dotnet build -c Debug -warnaserror osu.Desktop.slnf + + - name: Test + run: dotnet test $pwd/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" + shell: pwsh + + # Attempt to upload results even if test fails. + # https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always + - name: Upload Test Results + uses: actions/upload-artifact@v2 + if: ${{ always() }} + with: + name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} + path: ${{github.workspace}}/TestResults/TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx + + inspect-code: + name: Code Quality + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + # FIXME: Tools won't run in .NET 5.0 unless you install 3.1.x LTS side by side. + # https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e + - name: Install .NET 3.1.x LTS + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "3.1.x" + + - name: Install .NET 5.0.x + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "5.0.x" + + - name: Restore Tools + run: dotnet tool restore + + - name: Restore Packages + run: dotnet restore + + - name: CodeFileSanity + run: | + # TODO: Add ignore filters and GitHub Workflow Command Reporting in CFS. That way we don't have to do this workaround. + # FIXME: Suppress warnings from templates project + dotnet codefilesanity | while read -r line; do + echo "::warning::$line" + done + + # Temporarily disabled due to test failures, but it won't work anyway until the tool is upgraded. + # - name: .NET Format (Dry Run) + # run: dotnet format --dry-run --check + + - name: InspectCode + run: dotnet jb inspectcode $(pwd)/osu.Desktop.slnf --output=$(pwd)/inspectcodereport.xml --cachesDir=$(pwd)/inspectcode --verbosity=WARN + + - name: ReSharper + uses: glassechidna/resharper-action@master + with: + report: ${{github.workspace}}/inspectcodereport.xml diff --git a/.config/.github/workflows/report-nunit.yml b/.config/.github/workflows/report-nunit.yml new file mode 100644 index 0000000000..381d2d49c5 --- /dev/null +++ b/.config/.github/workflows/report-nunit.yml @@ -0,0 +1,31 @@ +# This is a workaround to allow PRs to report their coverage. This will run inside the base repository. +# See: +# * https://github.com/dorny/test-reporter#recommended-setup-for-public-repositories +# * https://docs.github.com/en/actions/reference/authentication-in-a-workflow#permissions-for-the-github_token +name: Annotate CI run with test results +on: + workflow_run: + workflows: ["Continuous Integration"] + types: + - completed +jobs: + annotate: + name: Annotate CI run with test results + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion != 'cancelled' }} + strategy: + fail-fast: false + matrix: + os: + - { prettyname: Windows } + - { prettyname: macOS } + - { prettyname: Linux } + threadingMode: ['SingleThread', 'MultiThreaded'] + steps: + - name: Annotate CI run with test results + uses: dorny/test-reporter@v1.4.2 + with: + artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} + name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}}) + path: "*.trx" + reporter: dotnet-trx From 5283948a6d2021d208609003fab4c553f4af9ffa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Jun 2021 22:05:13 +0900 Subject: [PATCH 350/520] Fix incorrect directory --- {.config/.github => .github}/workflows/ci.yml | 0 {.config/.github => .github}/workflows/report-nunit.yml | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {.config/.github => .github}/workflows/ci.yml (100%) rename {.config/.github => .github}/workflows/report-nunit.yml (100%) diff --git a/.config/.github/workflows/ci.yml b/.github/workflows/ci.yml similarity index 100% rename from .config/.github/workflows/ci.yml rename to .github/workflows/ci.yml diff --git a/.config/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml similarity index 100% rename from .config/.github/workflows/report-nunit.yml rename to .github/workflows/report-nunit.yml From a85a592f70245b01f8bde3db136f913912da67c4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 15 Jun 2021 16:16:25 +0300 Subject: [PATCH 351/520] Add lookup for spinner background colour --- osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs index 4e6d3ef0e4..f7ba8b9fc4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs @@ -7,6 +7,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { SliderTrackOverride, SliderBorder, - SliderBall + SliderBall, + SpinnerBackground, } } From 52145c9237815f56c211f9e6d7780b646ee98a58 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 15 Jun 2021 16:17:05 +0300 Subject: [PATCH 352/520] Assign skinnable colour to `spinner-background` with correct default --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs index 19cb55c16e..d80e061662 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -40,6 +41,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Anchor = Anchor.TopCentre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-background"), + Colour = source.GetConfig(OsuSkinColour.SpinnerBackground)?.Value ?? new Color4(100, 100, 100, 255), Scale = new Vector2(SPRITE_SCALE), Y = SPINNER_Y_CENTRE, }, From a4c4867d6a6b49db710b467ead9b2ec5afef0f0e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Jun 2021 22:40:31 +0900 Subject: [PATCH 353/520] Add scripts for running inspections locally --- InspectCode.ps1 | 11 +++++++++++ InspectCode.sh | 6 ++++++ appveyor.yml | 10 +--------- 3 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 InspectCode.ps1 create mode 100755 InspectCode.sh diff --git a/InspectCode.ps1 b/InspectCode.ps1 new file mode 100644 index 0000000000..8316f48ff3 --- /dev/null +++ b/InspectCode.ps1 @@ -0,0 +1,11 @@ +dotnet tool restore + +# Temporarily disabled until the tool is upgraded to 5.0. + # The version specified in .config/dotnet-tools.json (3.1.37601) won't run on .NET hosts >=5.0.7. + # - cmd: dotnet format --dry-run --check + +dotnet CodeFileSanity +dotnet jb inspectcode "osu.Desktop.slnf" --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN +dotnet nvika parsereport "inspectcodereport.xml" --treatwarningsaserrors + +exit $LASTEXITCODE diff --git a/InspectCode.sh b/InspectCode.sh new file mode 100755 index 0000000000..cf2bc18175 --- /dev/null +++ b/InspectCode.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +dotnet tool restore +dotnet CodeFileSanity +dotnet jb inspectcode "osu.Desktop.slnf" --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN +dotnet nvika parsereport "inspectcodereport.xml" --treatwarningsaserrors diff --git a/appveyor.yml b/appveyor.yml index accc913bf5..5be73f9875 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,15 +20,7 @@ build: publish_nuget: true after_build: - - cmd: dotnet tool restore - - # Temporarily disabled until the tool is upgraded to 5.0. - # The version specified in .config/dotnet-tools.json (3.1.37601) won't run on .NET hosts >=5.0.7. - # - cmd: dotnet format --dry-run --check - - - cmd: dotnet CodeFileSanity - - cmd: dotnet jb inspectcode "osu.Desktop.slnf" --output="temp/inspectcodereport.xml" --caches-home="temp/inspectcode" --verbosity=WARN - - cmd: dotnet nvika parsereport "temp/inspectcodereport.xml" --treatwarningsaserrors + - ps: .\InspectCode.ps1 test: assemblies: From e79e1bbcc0c694fc179c63018a59bc1df15e3a71 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Jun 2021 22:53:43 +0900 Subject: [PATCH 354/520] Fix malformed database test failing in single-threaded mode --- osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index a47631a83b..8f5ebf53bd 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -113,7 +113,6 @@ namespace osu.Game.Tests.Collections.IO await importCollectionsFromStream(osu, ms); } - Assert.That(host.UpdateThread.Running, Is.True); Assert.That(exceptionThrown, Is.False); Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(0)); } From 9d168b19c9c081af3f3daac920917e60a105e59d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 02:15:25 +0900 Subject: [PATCH 355/520] Switch to non-beta release --- 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 9c9d2e1a82..9f6bf0d2f4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From a549aebb3fbe9aa28b3b10dbd911c79f2e40d50c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 15 Jun 2021 22:32:26 +0200 Subject: [PATCH 356/520] Reword HD scale multiplier comment --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 00fcf5fa59..8a764a21bb 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -19,13 +19,16 @@ namespace osu.Game.Rulesets.Taiko.Mods public override string Description => @"Beats fade out before you hit them!"; public override double ScoreMultiplier => 1.06; - // In stable Taiko, hit position is 160, so playfield is essentially 160 pixels shorter - // than actual screen width. Normalized screen height is 480, so on a 4:3 screen the - // playfield ratio will actually be (640 - 160) / 480 = 1 - // For custom resolutions (x:y), screen width with normalized height becomes 480 * x / y instead, - // and the playfield ratio becomes (480 * x / y - 160) / 480 = x / y - 1/3 - // The following is 4:3 playfield ratio divided by 16:9 playfield ratio + /// + /// In stable taiko, the hit position is 160, so the active playfield is essentially 160 pixels shorter + /// than the actual screen width. The normalized playfield height is 480, so on a 4:3 screen the + /// playfield ratio of the active area up to the hit position will actually be (640 - 160) / 480 = 1. + /// For custom resolutions/aspect ratios (x:y), the screen width given the normalized height becomes 480 * x / y instead, + /// and the playfield ratio becomes (480 * x / y - 160) / 480 = x / y - 1/3. + /// This constant is equal to the playfield ratio on 4:3 screens divided by the playfield ratio on 16:9 screens. + /// private const double hd_sv_scale = (4.0 / 3.0 - 1.0 / 3.0) / (16.0 / 9.0 - 1.0 / 3.0); + private BeatmapDifficulty difficulty; private ControlPointInfo controlPointInfo; From 259e6cad4dd5e14111765d89b7030d834d2dae82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 15 Jun 2021 22:33:27 +0200 Subject: [PATCH 357/520] Rearrange and rename member --- .../Mods/TaikoModHidden.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 8a764a21bb..fd076b8765 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -19,6 +19,16 @@ namespace osu.Game.Rulesets.Taiko.Mods public override string Description => @"Beats fade out before you hit them!"; public override double ScoreMultiplier => 1.06; + [SettingSource("Fade-out Time", "The bigger this multiplier is, the sooner the notes will start fading out")] + public BindableNumber FadeOutTimeMultiplier { get; } = new BindableDouble + { + MinValue = 0.5, + MaxValue = 1.5, + Default = 1.0, + Value = 1.0, + Precision = 0.01, + }; + /// /// In stable taiko, the hit position is 160, so the active playfield is essentially 160 pixels shorter /// than the actual screen width. The normalized playfield height is 480, so on a 4:3 screen the @@ -32,16 +42,6 @@ namespace osu.Game.Rulesets.Taiko.Mods private BeatmapDifficulty difficulty; private ControlPointInfo controlPointInfo; - [SettingSource("Fade-out Time", "The bigger this multiplier is, the sooner the notes will start fading out")] - public BindableNumber VisibilityMod { get; } = new BindableDouble - { - MinValue = 0.5, - MaxValue = 1.5, - Default = 1.0, - Value = 1.0, - Precision = 0.01, - }; - protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { ApplyNormalVisibilityState(hitObject, state); @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Mods } // I *think* it's like this because stable's default velocity multiplier is 1.4 - var preempt = 14000 / MultiplierAt(hitObject.HitObject.StartTime) * VisibilityMod.Value; + var preempt = 14000 / MultiplierAt(hitObject.HitObject.StartTime) * FadeOutTimeMultiplier.Value; var start = hitObject.HitObject.StartTime - preempt * 0.6; var duration = preempt * 0.3; From b0549187df1339998fbf48267e74ae81c6233b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 15 Jun 2021 22:57:20 +0200 Subject: [PATCH 358/520] Apply pre-empt formula which is closer to stable --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index fd076b8765..613c16baa2 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Mods /// private const double hd_sv_scale = (4.0 / 3.0 - 1.0 / 3.0) / (16.0 / 9.0 - 1.0 / 3.0); - private BeatmapDifficulty difficulty; + private double originalSliderMultiplier; private ControlPointInfo controlPointInfo; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { var beatLength = controlPointInfo.TimingPointAt(position)?.BeatLength; var speedMultiplier = controlPointInfo.DifficultyPointAt(position)?.SpeedMultiplier; - return difficulty.SliderMultiplier * (speedMultiplier ?? 1.0) * TimingControlPoint.DEFAULT_BEAT_LENGTH / (beatLength ?? TimingControlPoint.DEFAULT_BEAT_LENGTH); + return originalSliderMultiplier * (speedMultiplier ?? 1.0) * TimingControlPoint.DEFAULT_BEAT_LENGTH / (beatLength ?? TimingControlPoint.DEFAULT_BEAT_LENGTH); } protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) @@ -68,8 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Mods return; } - // I *think* it's like this because stable's default velocity multiplier is 1.4 - var preempt = 14000 / MultiplierAt(hitObject.HitObject.StartTime) * FadeOutTimeMultiplier.Value; + var preempt = 10000 / MultiplierAt(hitObject.HitObject.StartTime) * FadeOutTimeMultiplier.Value; var start = hitObject.HitObject.StartTime - preempt * 0.6; var duration = preempt * 0.3; @@ -91,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public void ApplyToDifficulty(BeatmapDifficulty difficulty) { - this.difficulty = difficulty; + originalSliderMultiplier = difficulty.SliderMultiplier; difficulty.SliderMultiplier /= hd_sv_scale; } From 57f0c47dedb97d9fdce434ced9f1f8942baafd2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 15 Jun 2021 23:00:09 +0200 Subject: [PATCH 359/520] Ezplain slider multiplier adjustment --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 613c16baa2..5b7dca09a5 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -91,6 +91,11 @@ namespace osu.Game.Rulesets.Taiko.Mods public void ApplyToDifficulty(BeatmapDifficulty difficulty) { originalSliderMultiplier = difficulty.SliderMultiplier; + + // the hidden mod on stable had an added playfield cover that essentially forced a 4:3 playfield ratio, by cutting off all objects past that size. + // lazer currently uses a playfield adjustment container which keeps a 16:9 ratio. + // therefore, increase the slider multiplier proportionally so that the notes stay on the screen for the same amount of time as on stable. + // note that this will means that the notes will scroll faster as they have a longer distance to travel on the screen in that same amount of time. difficulty.SliderMultiplier /= hd_sv_scale; } From 30703d518c055e3f6e5656f5bdd23952dbbc0c4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 15 Jun 2021 23:19:33 +0200 Subject: [PATCH 360/520] Add failing assert for seasonal background equality --- .../Visual/Background/TestSceneSeasonalBackgroundLoader.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs index dc5a4f4a3e..0bd1263076 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs @@ -161,15 +161,18 @@ namespace osu.Game.Tests.Visual.Background private void loadNextBackground() { + SeasonalBackground previousBackground = null; SeasonalBackground background = null; AddStep("create next background", () => { + previousBackground = (SeasonalBackground)backgroundContainer.SingleOrDefault(); background = backgroundLoader.LoadNextBackground(); LoadComponentAsync(background, bg => backgroundContainer.Child = bg); }); AddUntilStep("background loaded", () => background.IsLoaded); + AddAssert("background is different", () => !background.Equals(previousBackground)); } private void assertAnyBackground() From 022b1a28d5d491bad38676eb961ad12f491c74bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 15 Jun 2021 23:21:48 +0200 Subject: [PATCH 361/520] Add missing equality implementation for seasonal backgrounds The equality operator is used to determine whether the next background in the cycle should be loaded, to avoid pointless loads of the same background several times (see #13362 and #13393). Its omission in the latter pull caused seasonal backgrounds to no longer cycle. Closes #13508. --- .../Graphics/Backgrounds/SeasonalBackgroundLoader.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index a48da37804..f01a26a3a8 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -99,5 +99,14 @@ namespace osu.Game.Graphics.Backgrounds // ensure we're not loading in without a transition. this.FadeInFromZero(200, Easing.InOutSine); } + + public override bool Equals(Background other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return other.GetType() == GetType() + && ((SeasonalBackground)other).url == url; + } } } From 8c558610abe06b6ed6f23eb91bd83f16f390fc07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Jun 2021 00:34:39 +0200 Subject: [PATCH 362/520] Fix hitobjects expiring before fully judged with hidden --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 5b7dca09a5..15fc8c130e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.Mods // DrawableHitObject sets LifetimeEnd to LatestTransformEndTime if it isn't manually changed. // in order for the object to not be killed before its actual end time (as the latest transform ends earlier), set lifetime end explicitly. - hitObject.LifetimeEnd = state == ArmedState.Idle + hitObject.LifetimeEnd = state == ArmedState.Idle || !hitObject.AllJudged ? hitObject.HitObject.GetEndTime() + hitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) : hitObject.HitStateUpdateTime; } From 6be41e497a2a9556ac4fc8334e9a5bc90fe4193a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Jun 2021 11:25:00 +0900 Subject: [PATCH 363/520] Fix possible nullref in difficulty recommender --- osu.Game/Beatmaps/DifficultyRecommender.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 340c47d89b..ca910e70b8 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -101,10 +101,20 @@ namespace osu.Game.Beatmaps /// Rulesets ordered descending by their respective recommended difficulties. /// The currently selected ruleset will always be first. /// - private IEnumerable orderedRulesets => - recommendedDifficultyMapping - .OrderByDescending(pair => pair.Value).Select(pair => pair.Key).Where(r => !r.Equals(ruleset.Value)) - .Prepend(ruleset.Value); + private IEnumerable orderedRulesets + { + get + { + if (LoadState < LoadState.Ready || ruleset.Value == null) + return Enumerable.Empty(); + + return recommendedDifficultyMapping + .OrderByDescending(pair => pair.Value) + .Select(pair => pair.Key) + .Where(r => !r.Equals(ruleset.Value)) + .Prepend(ruleset.Value); + } + } private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { From a5261f0cb3c7209bfc36ba157c02c80020ec18fa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Jun 2021 11:48:41 +0900 Subject: [PATCH 364/520] Add difficulty recommender instantly --- 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 019d3b3cd0..7f23dfc7f8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -712,7 +712,6 @@ namespace osu.Game PostNotification = n => notifications.Post(n), }, Add, true); - loadComponentSingleFile(difficultyRecommender, Add); loadComponentSingleFile(stableImportManager, Add); loadComponentSingleFile(screenshotManager, Add); @@ -755,6 +754,7 @@ namespace osu.Game chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; + Add(difficultyRecommender); Add(externalLinkOpener = new ExternalLinkOpener()); Add(new MusicKeyBindingHandler()); From 451ce04d19126a4c38c096e90fa97a94231e81dd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Jun 2021 13:22:46 +0900 Subject: [PATCH 365/520] Make Resharper inspections fail CI job As per https://github.com/ppy/osu-framework/pull/4514. --- .config/dotnet-tools.json | 4 ++-- .github/workflows/ci.yml | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 1dca8b3859..b3f7c67c51 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -14,8 +14,8 @@ "jb" ] }, - "nvika": { - "version": "2.0.0", + "smoogipoo.nvika": { + "version": "1.0.1", "commands": [ "nvika" ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0be3f64ab3..ed3e99cb61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,7 +89,5 @@ jobs: - name: InspectCode run: dotnet jb inspectcode $(pwd)/osu.Desktop.slnf --output=$(pwd)/inspectcodereport.xml --cachesDir=$(pwd)/inspectcode --verbosity=WARN - - name: ReSharper - uses: glassechidna/resharper-action@master - with: - report: ${{github.workspace}}/inspectcodereport.xml + - name: NVika + run: dotnet nvika parsereport "${{github.workspace}}/inspectcodereport.xml" --treatwarningsaserrors From fa00d07107a9079af55ef6d2ea4a9a3c16c7e29e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Jun 2021 13:26:36 +0900 Subject: [PATCH 366/520] Upgrade osu-resources --- 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 c020b1d783..490e43b5e6 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a7bd5f2e9f..8eeaad1127 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 5b3efb4ba4..db442238ce 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 4c5268694efca63bed526a5f0a2b57ecef4a3699 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Jun 2021 13:46:13 +0900 Subject: [PATCH 367/520] Localise some of the BeatmapListingOverlay --- .../BeatmapListingSearchControl.cs | 17 +++++----- .../BeatmapListing/BeatmapSearchFilterRow.cs | 6 ++-- ...BeatmapSearchMultipleSelectionFilterRow.cs | 5 +-- .../BeatmapSearchRulesetFilterRow.cs | 3 +- .../BeatmapSearchScoreFilterRow.cs | 32 +++++++++++++++---- .../Overlays/BeatmapListing/FilterTabItem.cs | 3 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 3 +- 7 files changed, 47 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 97ccb66599..0626f236b8 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -14,6 +14,7 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Resources.Localisation.Web; using osuTK.Graphics; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -126,15 +127,15 @@ namespace osu.Game.Overlays.BeatmapListing Padding = new MarginPadding { Horizontal = 10 }, Children = new Drawable[] { - generalFilter = new BeatmapSearchMultipleSelectionFilterRow(@"General"), + generalFilter = new BeatmapSearchMultipleSelectionFilterRow(BeatmapsStrings.ListingSearchFiltersGeneral), modeFilter = new BeatmapSearchRulesetFilterRow(), - categoryFilter = new BeatmapSearchFilterRow(@"Categories"), - genreFilter = new BeatmapSearchFilterRow(@"Genre"), - languageFilter = new BeatmapSearchFilterRow(@"Language"), - extraFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Extra"), + categoryFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersStatus), + genreFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersGenre), + languageFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersLanguage), + extraFilter = new BeatmapSearchMultipleSelectionFilterRow(BeatmapsStrings.ListingSearchFiltersExtra), ranksFilter = new BeatmapSearchScoreFilterRow(), - playedFilter = new BeatmapSearchFilterRow(@"Played"), - explicitContentFilter = new BeatmapSearchFilterRow(@"Explicit Content"), + playedFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersPlayed), + explicitContentFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersNsfw), } } } @@ -172,7 +173,7 @@ namespace osu.Game.Overlays.BeatmapListing public BeatmapSearchTextBox() { - PlaceholderText = @"type in keywords..."; + PlaceholderText = BeatmapsStrings.ListingSearchPrompt; } protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index 01bcbd3244..4c831543fe 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -11,8 +11,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; -using Humanizer; using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Localisation; namespace osu.Game.Overlays.BeatmapListing { @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapListing set => current.Current = value; } - public BeatmapSearchFilterRow(string headerName) + public BeatmapSearchFilterRow(LocalisableString header) { Drawable filter; AutoSizeAxes = Axes.Y; @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapListing Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Font = OsuFont.GetFont(size: 13), - Text = headerName.Titleize() + Text = header }, filter = CreateFilter() } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 5dfa8e6109..e0632ace58 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osuTK; namespace osu.Game.Overlays.BeatmapListing @@ -19,8 +20,8 @@ namespace osu.Game.Overlays.BeatmapListing private MultipleSelectionFilter filter; - public BeatmapSearchMultipleSelectionFilterRow(string headerName) - : base(headerName) + public BeatmapSearchMultipleSelectionFilterRow(LocalisableString header) + : base(header) { Current.BindTo(filter.Current); } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs index a8dc088e52..c2d0eea80c 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; namespace osu.Game.Overlays.BeatmapListing @@ -10,7 +11,7 @@ namespace osu.Game.Overlays.BeatmapListing public class BeatmapSearchRulesetFilterRow : BeatmapSearchFilterRow { public BeatmapSearchRulesetFilterRow() - : base(@"Mode") + : base(BeatmapsStrings.ListingSearchFiltersMode) { } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs index 804962adfb..abfffe907f 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.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 System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Extensions; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapListing @@ -11,7 +13,7 @@ namespace osu.Game.Overlays.BeatmapListing public class BeatmapSearchScoreFilterRow : BeatmapSearchMultipleSelectionFilterRow { public BeatmapSearchScoreFilterRow() - : base(@"Rank Achieved") + : base(BeatmapsStrings.ListingSearchFiltersRank) { } @@ -31,18 +33,36 @@ namespace osu.Game.Overlays.BeatmapListing { } - protected override string LabelFor(ScoreRank value) + protected override LocalisableString LabelFor(ScoreRank value) { switch (value) { case ScoreRank.XH: - return @"Silver SS"; + return BeatmapsStrings.RankXH; + + case ScoreRank.X: + return BeatmapsStrings.RankX; case ScoreRank.SH: - return @"Silver S"; + return BeatmapsStrings.RankSH; + + case ScoreRank.S: + return BeatmapsStrings.RankS; + + case ScoreRank.A: + return BeatmapsStrings.RankA; + + case ScoreRank.B: + return BeatmapsStrings.RankB; + + case ScoreRank.C: + return BeatmapsStrings.RankC; + + case ScoreRank.D: + return BeatmapsStrings.RankD; default: - return value.GetDescription(); + throw new ArgumentException("Unsupported value.", nameof(value)); } } } diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index f02b515755..d64ee59682 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -66,7 +67,7 @@ namespace osu.Game.Overlays.BeatmapListing /// /// Returns the label text to be used for the supplied . /// - protected virtual string LabelFor(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + protected virtual LocalisableString LabelFor(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); private void updateState() { diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5df7a4650e..5e65cd9488 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -18,6 +18,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing.Panels; +using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -232,7 +233,7 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = @"... nope, nothing found.", + Text = BeatmapsStrings.ListingSearchNotFoundQuote, } } }); From 73e443a0d9c5bc67192fef049d33dc8f477decb5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 14:01:12 +0900 Subject: [PATCH 368/520] Add comments --- osu.Game.Rulesets.Catch/UI/CatcherTrail.cs | 6 ++++++ osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs index 90fb59db9a..f273ef23ac 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs @@ -7,6 +7,12 @@ using osuTK; namespace osu.Game.Rulesets.Catch.UI { + /// + /// A trail of the catcher. + /// It also represents a hyper dash afterimage. + /// + // TODO: Trails shouldn't be animated when the skin has an animated catcher. + // The animation should be frozen at the animation frame at the time of the trail generation. public class CatcherTrail : PoolableDrawable { public CatcherAnimationState AnimationState diff --git a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs index 5bd97858b2..1038af7a48 100644 --- a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs +++ b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs @@ -10,6 +10,10 @@ using osuTK; namespace osu.Game.Rulesets.Catch.UI { + /// + /// The visual representation of the . + /// It includes the body part of the catcher and the catcher plate. + /// public class SkinnableCatcher : SkinnableDrawable { [Cached] From 2ce487bdacf8dc105d12a63f6613535585bb4823 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 14:24:07 +0900 Subject: [PATCH 369/520] Rename mod and fix easing mappings / naming --- .../Mods/OsuModApproachCircle.cs | 73 ------------ .../Mods/OsuModDifferentApproach.cs | 110 ++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 3 files changed, 111 insertions(+), 74 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModDifferentApproach.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs deleted file mode 100644 index 8e0175b5a3..0000000000 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachCircle.cs +++ /dev/null @@ -1,73 +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 System.Collections.Generic; -using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Configuration; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables; - -namespace osu.Game.Rulesets.Osu.Mods -{ - internal class OsuModApproachCircle : Mod, IApplicableToDrawableHitObjects - { - public override string Name => "Approach Circle"; - public override string Acronym => "AC"; - public override string Description => "Never trust the approach circles..."; - public override double ScoreMultiplier => 1; - public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle; - - [SettingSource("Easing", "Change the easing type of the approach circles.", 0)] - public Bindable BindableEasing { get; } = new Bindable(); - - [SettingSource("Scale the size", "Change the factor of the approach circle scale.", 1)] - public BindableFloat Scale { get; } = new BindableFloat - { - Precision = 0.1f, - MinValue = 2, - MaxValue = 10, - Default = 4, - Value = 4 - }; - - public void ApplyToDrawableHitObjects(IEnumerable drawables) - { - drawables.ForEach(drawable => - { - drawable.ApplyCustomUpdateState += (drawableHitObj, state) => - { - if (!(drawableHitObj is DrawableHitCircle hitCircle)) return; - - var obj = hitCircle.HitObject; - - hitCircle.BeginAbsoluteSequence(obj.StartTime - obj.TimePreempt, true); - hitCircle.ApproachCircle.ScaleTo(Scale.Value); - - hitCircle.ApproachCircle.FadeIn(Math.Min(obj.TimeFadeIn, obj.TimePreempt)); - - hitCircle.ApproachCircle.ScaleTo(1f, obj.TimePreempt, (Easing)BindableEasing.Value); - - hitCircle.ApproachCircle.Expire(true); - }; - }); - } - - internal enum ReadableEasing - { - Accelerate = 2, - Accelerate2 = 6, - Accelerate3 = 12, - AccelerateInAfterDeceleraingeOut = 29, - CasualBounces = 32, - CasualBouncesWhileAccelerating = 24, - Decelerate = 1, - DecelerateAfterAccelerating = 8, - Default = 0, - } - } -} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifferentApproach.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifferentApproach.cs new file mode 100644 index 0000000000..db612b6269 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifferentApproach.cs @@ -0,0 +1,110 @@ +// 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 osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Mods +{ + internal class OsuModDifferentApproach : Mod, IApplicableToDrawableHitObjects + { + public override string Name => "Approach Different"; + public override string Acronym => "AD"; + public override string Description => "Never trust the approach circles..."; + public override double ScoreMultiplier => 1; + public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle; + + [SettingSource("Easing", "Change the animation curve of the approach circles.", 0)] + public Bindable BindableEasing { get; } = new Bindable(); + + [SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 1)] + public BindableFloat Scale { get; } = new BindableFloat + { + Precision = 0.1f, + MinValue = 2, + MaxValue = 10, + Default = 4, + Value = 4 + }; + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + drawables.ForEach(drawable => + { + drawable.ApplyCustomUpdateState += (drawableHitObj, state) => + { + if (!(drawableHitObj is DrawableHitCircle hitCircle)) return; + + var obj = hitCircle.HitObject; + + hitCircle.BeginAbsoluteSequence(obj.StartTime - obj.TimePreempt, true); + hitCircle.ApproachCircle.ScaleTo(Scale.Value); + + hitCircle.ApproachCircle.FadeIn(Math.Min(obj.TimeFadeIn, obj.TimePreempt)); + + hitCircle.ApproachCircle.ScaleTo(1f, obj.TimePreempt, getEasing(BindableEasing.Value)); + + hitCircle.ApproachCircle.Expire(true); + }; + }); + } + + private Easing getEasing(ApproachCircleEasing approachEasing) + { + switch (approachEasing) + { + default: + return Easing.None; + + case ApproachCircleEasing.Accelerate1: + return Easing.In; + + case ApproachCircleEasing.Accelerate2: + return Easing.InCubic; + + case ApproachCircleEasing.Accelerate3: + return Easing.InQuint; + + case ApproachCircleEasing.Gravity: + return Easing.InBack; + + case ApproachCircleEasing.Decelerate1: + return Easing.Out; + + case ApproachCircleEasing.Decelerate2: + return Easing.OutCubic; + + case ApproachCircleEasing.Decelerate3: + return Easing.OutQuint; + + case ApproachCircleEasing.InOut1: + return Easing.InOutCubic; + + case ApproachCircleEasing.InOut2: + return Easing.InOutQuint; + } + } + + public enum ApproachCircleEasing + { + Default, + Accelerate1, + Accelerate2, + Accelerate3, + Gravity, + Decelerate1, + Decelerate2, + Decelerate3, + InOut1, + InOut2, + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 0f7ee6ade2..217803ee7b 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new ModWindUp(), new ModWindDown()), new OsuModTraceable(), new OsuModBarrelRoll(), - new OsuModApproachCircle(), + new OsuModDifferentApproach(), }; case ModType.System: From b1dd502e062298c00022cefbf7bbe107d58ad032 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 15:09:42 +0900 Subject: [PATCH 370/520] Rename class to match new name --- .../{OsuModDifferentApproach.cs => OsuModApproachDifferent.cs} | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/Mods/{OsuModDifferentApproach.cs => OsuModApproachDifferent.cs} (97%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifferentApproach.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs similarity index 97% rename from osu.Game.Rulesets.Osu/Mods/OsuModDifferentApproach.cs rename to osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index db612b6269..87358112b3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifferentApproach.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs @@ -14,7 +14,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModDifferentApproach : Mod, IApplicableToDrawableHitObjects + public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObjects { public override string Name => "Approach Different"; public override string Acronym => "AD"; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 217803ee7b..1b9bcd19fd 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new ModWindUp(), new ModWindDown()), new OsuModTraceable(), new OsuModBarrelRoll(), - new OsuModDifferentApproach(), + new OsuModApproachDifferent(), }; case ModType.System: From f6f1a068b252bc2e72b364a82f6231dbdbd73912 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 15:15:12 +0900 Subject: [PATCH 371/520] Rename "easing" references to be "style" instead --- .../Mods/OsuModApproachDifferent.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index 87358112b3..cd718a7b18 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs @@ -22,8 +22,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle; - [SettingSource("Easing", "Change the animation curve of the approach circles.", 0)] - public Bindable BindableEasing { get; } = new Bindable(); + [SettingSource("Style", "Change the animation style of the approach circles.", 0)] + public Bindable Style { get; } = new Bindable(); [SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 1)] public BindableFloat Scale { get; } = new BindableFloat @@ -50,50 +50,50 @@ namespace osu.Game.Rulesets.Osu.Mods hitCircle.ApproachCircle.FadeIn(Math.Min(obj.TimeFadeIn, obj.TimePreempt)); - hitCircle.ApproachCircle.ScaleTo(1f, obj.TimePreempt, getEasing(BindableEasing.Value)); + hitCircle.ApproachCircle.ScaleTo(1f, obj.TimePreempt, getEasing(Style.Value)); hitCircle.ApproachCircle.Expire(true); }; }); } - private Easing getEasing(ApproachCircleEasing approachEasing) + private Easing getEasing(AnimationStyle approachEasing) { switch (approachEasing) { default: return Easing.None; - case ApproachCircleEasing.Accelerate1: + case AnimationStyle.Accelerate1: return Easing.In; - case ApproachCircleEasing.Accelerate2: + case AnimationStyle.Accelerate2: return Easing.InCubic; - case ApproachCircleEasing.Accelerate3: + case AnimationStyle.Accelerate3: return Easing.InQuint; - case ApproachCircleEasing.Gravity: + case AnimationStyle.Gravity: return Easing.InBack; - case ApproachCircleEasing.Decelerate1: + case AnimationStyle.Decelerate1: return Easing.Out; - case ApproachCircleEasing.Decelerate2: + case AnimationStyle.Decelerate2: return Easing.OutCubic; - case ApproachCircleEasing.Decelerate3: + case AnimationStyle.Decelerate3: return Easing.OutQuint; - case ApproachCircleEasing.InOut1: + case AnimationStyle.InOut1: return Easing.InOutCubic; - case ApproachCircleEasing.InOut2: + case AnimationStyle.InOut2: return Easing.InOutQuint; } } - public enum ApproachCircleEasing + public enum AnimationStyle { Default, Accelerate1, From 3c3ff8be0d0594a029850678f823c471d40a6890 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Jun 2021 15:58:07 +0900 Subject: [PATCH 372/520] Localise beatmap listing enum values --- .../Graphics/UserInterface/OsuTabControl.cs | 2 +- .../Graphics/UserInterface/PageTabControl.cs | 3 +- .../Overlays/BeatmapListing/FilterTabItem.cs | 2 +- .../Overlays/BeatmapListing/SearchCategory.cs | 43 +++++++++++++ .../Overlays/BeatmapListing/SearchExplicit.cs | 23 +++++++ .../Overlays/BeatmapListing/SearchExtra.cs | 22 +++++++ .../Overlays/BeatmapListing/SearchGeneral.cs | 25 ++++++++ .../Overlays/BeatmapListing/SearchGenre.cs | 58 ++++++++++++++++++ .../Overlays/BeatmapListing/SearchLanguage.cs | 61 +++++++++++++++++++ .../Overlays/BeatmapListing/SearchPlayed.cs | 34 +++++++++-- .../Overlays/BeatmapListing/SortCriteria.cs | 41 +++++++++++++ osu.Game/Overlays/OverlaySortTabControl.cs | 2 +- osu.Game/Overlays/TabControlOverlayHeader.cs | 14 ++++- 13 files changed, 321 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index dbcce9a84a..0c220336a5 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -160,7 +160,7 @@ namespace osu.Game.Graphics.UserInterface Margin = new MarginPadding { Top = 5, Bottom = 5 }, Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, - Text = (value as IHasDescription)?.Description ?? (value as Enum)?.GetDescription() ?? value.ToString(), + Text = (value as IHasDescription)?.Description ?? (value as Enum)?.GetLocalisableDescription() ?? value.ToString(), Font = OsuFont.GetFont(size: 14) }, Bar = new Box diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs index d05a08108a..1ba9ad53bb 100644 --- a/osu.Game/Graphics/UserInterface/PageTabControl.cs +++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface @@ -81,7 +82,7 @@ namespace osu.Game.Graphics.UserInterface Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Torus, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true); } - protected virtual string CreateText() => (Value as Enum)?.GetDescription() ?? Value.ToString(); + protected virtual LocalisableString CreateText() => (Value as Enum)?.GetLocalisableDescription() ?? Value.ToString(); protected override bool OnHover(HoverEvent e) { diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index d64ee59682..46cb1e822f 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.BeatmapListing /// /// Returns the label text to be used for the supplied . /// - protected virtual LocalisableString LabelFor(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + protected virtual LocalisableString LabelFor(T value) => (value as Enum)?.GetLocalisableDescription() ?? value.ToString(); private void updateState() { diff --git a/osu.Game/Overlays/BeatmapListing/SearchCategory.cs b/osu.Game/Overlays/BeatmapListing/SearchCategory.cs index 84859bf5b5..8a9df76af3 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchCategory.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchCategory.cs @@ -1,10 +1,14 @@ // 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.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SearchCategoryEnumLocalisationMapper))] public enum SearchCategory { Any, @@ -23,4 +27,43 @@ namespace osu.Game.Overlays.BeatmapListing [Description("My Maps")] Mine, } + + public class SearchCategoryEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchCategory value) + { + switch (value) + { + case SearchCategory.Any: + return BeatmapsStrings.StatusAny; + + case SearchCategory.Leaderboard: + return BeatmapsStrings.StatusLeaderboard; + + case SearchCategory.Ranked: + return BeatmapsStrings.StatusRanked; + + case SearchCategory.Qualified: + return BeatmapsStrings.StatusQualified; + + case SearchCategory.Loved: + return BeatmapsStrings.StatusLoved; + + case SearchCategory.Favourites: + return BeatmapsStrings.StatusFavourites; + + case SearchCategory.Pending: + return BeatmapsStrings.StatusPending; + + case SearchCategory.Graveyard: + return BeatmapsStrings.StatusGraveyard; + + case SearchCategory.Mine: + return BeatmapsStrings.StatusMine; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchExplicit.cs b/osu.Game/Overlays/BeatmapListing/SearchExplicit.cs index 3e57cdd48c..78e6a4e094 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExplicit.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExplicit.cs @@ -1,11 +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 System; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SearchExplicitEnumLocalisationMapper))] public enum SearchExplicit { Hide, Show } + + public class SearchExplicitEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchExplicit value) + { + switch (value) + { + case SearchExplicit.Hide: + return BeatmapsStrings.NsfwExclude; + + case SearchExplicit.Show: + return BeatmapsStrings.NsfwInclude; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index af37e3264f..4b3fb6e833 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -1,10 +1,14 @@ // 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.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SearchExtraEnumLocalisationMapper))] public enum SearchExtra { [Description("Has Video")] @@ -13,4 +17,22 @@ namespace osu.Game.Overlays.BeatmapListing [Description("Has Storyboard")] Storyboard } + + public class SearchExtraEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchExtra value) + { + switch (value) + { + case SearchExtra.Video: + return BeatmapsStrings.ExtraVideo; + + case SearchExtra.Storyboard: + return BeatmapsStrings.ExtraStoryboard; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs b/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs index 175942c626..b4c629f7fa 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs @@ -1,10 +1,14 @@ // 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.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SearchGeneralEnumLocalisationMapper))] public enum SearchGeneral { [Description("Recommended difficulty")] @@ -16,4 +20,25 @@ namespace osu.Game.Overlays.BeatmapListing [Description("Subscribed mappers")] Follows } + + public class SearchGeneralEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchGeneral value) + { + switch (value) + { + case SearchGeneral.Recommended: + return BeatmapsStrings.GeneralRecommended; + + case SearchGeneral.Converts: + return BeatmapsStrings.GeneralConverts; + + case SearchGeneral.Follows: + return BeatmapsStrings.GeneralFollows; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchGenre.cs b/osu.Game/Overlays/BeatmapListing/SearchGenre.cs index de437fac3e..b2709ecd2e 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchGenre.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchGenre.cs @@ -1,10 +1,14 @@ // 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.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SearchGenreEnumLocalisationMapper))] public enum SearchGenre { Any = 0, @@ -26,4 +30,58 @@ namespace osu.Game.Overlays.BeatmapListing Folk = 13, Jazz = 14 } + + public class SearchGenreEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchGenre value) + { + switch (value) + { + case SearchGenre.Any: + return BeatmapsStrings.GenreAny; + + case SearchGenre.Unspecified: + return BeatmapsStrings.GenreUnspecified; + + case SearchGenre.VideoGame: + return BeatmapsStrings.GenreVideoGame; + + case SearchGenre.Anime: + return BeatmapsStrings.GenreAnime; + + case SearchGenre.Rock: + return BeatmapsStrings.GenreRock; + + case SearchGenre.Pop: + return BeatmapsStrings.GenrePop; + + case SearchGenre.Other: + return BeatmapsStrings.GenreOther; + + case SearchGenre.Novelty: + return BeatmapsStrings.GenreNovelty; + + case SearchGenre.HipHop: + return BeatmapsStrings.GenreHipHop; + + case SearchGenre.Electronic: + return BeatmapsStrings.GenreElectronic; + + case SearchGenre.Metal: + return BeatmapsStrings.GenreMetal; + + case SearchGenre.Classical: + return BeatmapsStrings.GenreClassical; + + case SearchGenre.Folk: + return BeatmapsStrings.GenreFolk; + + case SearchGenre.Jazz: + return BeatmapsStrings.GenreJazz; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs index 015cee8ce3..352383d576 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs @@ -1,10 +1,14 @@ // 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.Localisation; using osu.Framework.Utils; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SearchLanguageEnumLocalisationMapper))] [HasOrderedElements] public enum SearchLanguage { @@ -53,4 +57,61 @@ namespace osu.Game.Overlays.BeatmapListing [Order(13)] Other } + + public class SearchLanguageEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchLanguage value) + { + switch (value) + { + case SearchLanguage.Any: + return BeatmapsStrings.LanguageAny; + + case SearchLanguage.Unspecified: + return BeatmapsStrings.LanguageUnspecified; + + case SearchLanguage.English: + return BeatmapsStrings.LanguageEnglish; + + case SearchLanguage.Japanese: + return BeatmapsStrings.LanguageJapanese; + + case SearchLanguage.Chinese: + return BeatmapsStrings.LanguageChinese; + + case SearchLanguage.Instrumental: + return BeatmapsStrings.LanguageInstrumental; + + case SearchLanguage.Korean: + return BeatmapsStrings.LanguageKorean; + + case SearchLanguage.French: + return BeatmapsStrings.LanguageFrench; + + case SearchLanguage.German: + return BeatmapsStrings.LanguageGerman; + + case SearchLanguage.Swedish: + return BeatmapsStrings.LanguageSwedish; + + case SearchLanguage.Spanish: + return BeatmapsStrings.LanguageSpanish; + + case SearchLanguage.Italian: + return BeatmapsStrings.LanguageItalian; + + case SearchLanguage.Russian: + return BeatmapsStrings.LanguageRussian; + + case SearchLanguage.Polish: + return BeatmapsStrings.LanguagePolish; + + case SearchLanguage.Other: + return BeatmapsStrings.GenreOther; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs b/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs index eb7fb46158..93c0644d45 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs @@ -1,12 +1,38 @@ // 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.Localisation; +using osu.Game.Resources.Localisation.Web; + namespace osu.Game.Overlays.BeatmapListing { - public enum SearchPlayed +[LocalisableEnum(typeof(SearchPlayedEnumLocalisationMapper))] +public enum SearchPlayed +{ + Any, + Played, + Unplayed +} + +public class SearchPlayedEnumLocalisationMapper : EnumLocalisationMapper +{ + public override LocalisableString Map(SearchPlayed value) { - Any, - Played, - Unplayed + switch (value) + { + case SearchPlayed.Any: + return BeatmapsStrings.PlayedAny; + + case SearchPlayed.Played: + return BeatmapsStrings.PlayedPlayed; + + case SearchPlayed.Unplayed: + return BeatmapsStrings.PlayedUnplayed; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } } } +} diff --git a/osu.Game/Overlays/BeatmapListing/SortCriteria.cs b/osu.Game/Overlays/BeatmapListing/SortCriteria.cs index e409cbdda7..5ea885eecc 100644 --- a/osu.Game/Overlays/BeatmapListing/SortCriteria.cs +++ b/osu.Game/Overlays/BeatmapListing/SortCriteria.cs @@ -1,8 +1,13 @@ // 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.Localisation; +using osu.Game.Resources.Localisation.Web; + namespace osu.Game.Overlays.BeatmapListing { + [LocalisableEnum(typeof(SortCriteriaLocalisationMapper))] public enum SortCriteria { Title, @@ -14,4 +19,40 @@ namespace osu.Game.Overlays.BeatmapListing Favourites, Relevance } + + public class SortCriteriaLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SortCriteria value) + { + switch (value) + { + case SortCriteria.Title: + return BeatmapsStrings.ListingSearchSortingTitle; + + case SortCriteria.Artist: + return BeatmapsStrings.ListingSearchSortingArtist; + + case SortCriteria.Difficulty: + return BeatmapsStrings.ListingSearchSortingDifficulty; + + case SortCriteria.Ranked: + return BeatmapsStrings.ListingSearchSortingRanked; + + case SortCriteria.Rating: + return BeatmapsStrings.ListingSearchSortingRating; + + case SortCriteria.Plays: + return BeatmapsStrings.ListingSearchSortingPlays; + + case SortCriteria.Favourites: + return BeatmapsStrings.ListingSearchSortingFavourites; + + case SortCriteria.Relevance: + return BeatmapsStrings.ListingSearchSortingRelevance; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/OverlaySortTabControl.cs b/osu.Game/Overlays/OverlaySortTabControl.cs index 0ebabd424f..5ece3e4019 100644 --- a/osu.Game/Overlays/OverlaySortTabControl.cs +++ b/osu.Game/Overlays/OverlaySortTabControl.cs @@ -143,7 +143,7 @@ namespace osu.Game.Overlays Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = (value as Enum)?.GetDescription() ?? value.ToString() + Text = (value as Enum)?.GetLocalisableDescription() ?? value.ToString() } } }); diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index 7798dfa576..e6f7e250a7 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -106,7 +106,19 @@ namespace osu.Game.Overlays public OverlayHeaderTabItem(T value) : base(value) { - Text.Text = ((Value as Enum)?.GetDescription() ?? Value.ToString()).ToLower(); + if (!(Value is Enum enumValue)) + Text.Text = Value.ToString().ToLower(); + else + { + var localisableDescription = enumValue.GetLocalisableDescription(); + var nonLocalisableDescription = enumValue.GetDescription(); + + // If localisable == non-localisable, then we must have a basic string, so .ToLower() is used. + Text.Text = localisableDescription.Equals(nonLocalisableDescription) + ? nonLocalisableDescription.ToLower() + : localisableDescription; + } + Text.Font = OsuFont.GetFont(size: 14); Text.Margin = new MarginPadding { Vertical = 16.5f }; // 15px padding + 1.5px line-height difference compensation Bar.Margin = new MarginPadding { Bottom = bar_height }; From 117e94bc94c2094d403cb0518f6fb5ecb2758b64 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 15:58:17 +0900 Subject: [PATCH 373/520] Allow setting `Entry` of `PoolableDrawableWithLifetime` It is more convenient than using the constructor because the only limited kind of expression is allowed in a base constructor call. Also, the object initializer syntax can be used. --- .../Objects/Drawables/DrawableHitObject.cs | 7 ++-- .../Pooling/PoolableDrawableWithLifetime.cs | 35 ++++++++++++++----- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7fc35fc778..a0717ec38e 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -156,10 +156,11 @@ namespace osu.Game.Rulesets.Objects.Drawables /// If null, a hitobject is expected to be later applied via (or automatically via pooling). /// protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) - : base(initialHitObject != null ? new SyntheticHitObjectEntry(initialHitObject) : null) { - if (Entry != null) - ensureEntryHasResult(); + if (initialHitObject == null) return; + + Entry = new SyntheticHitObjectEntry(initialHitObject); + ensureEntryHasResult(); } [BackgroundDependencyLoader] diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 4440ca8d21..d5d1a7b55c 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; +using osu.Framework.Graphics; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; @@ -16,14 +17,32 @@ namespace osu.Game.Rulesets.Objects.Pooling /// The type storing state and controlling this drawable. public abstract class PoolableDrawableWithLifetime : PoolableDrawable where TEntry : LifetimeEntry { + private TEntry? entry; + /// /// The entry holding essential state of this . /// - public TEntry? Entry { get; private set; } + /// + /// If a non-null value is set before loading is started, the entry is applied when the loading is completed. + /// + public TEntry? Entry + { + get => entry; + + set + { + if (LoadState < LoadState.Ready) + entry = value; + else if (value != null) + Apply(value); + else if (HasEntryApplied) + free(); + } + } /// /// Whether is applied to this . - /// When an initial entry is specified in the constructor, is set but not applied until loading is completed. + /// When an is set during initialization, it is not applied until loading is completed. /// protected bool HasEntryApplied { get; private set; } @@ -65,7 +84,7 @@ namespace osu.Game.Rulesets.Objects.Pooling { base.LoadAsyncComplete(); - // Apply the initial entry given in the constructor. + // Apply the initial entry. if (Entry != null && !HasEntryApplied) Apply(Entry); } @@ -79,7 +98,7 @@ namespace osu.Game.Rulesets.Objects.Pooling if (HasEntryApplied) free(); - Entry = entry; + this.entry = entry; entry.LifetimeChanged += setLifetimeFromEntry; setLifetimeFromEntry(entry); @@ -113,12 +132,12 @@ namespace osu.Game.Rulesets.Objects.Pooling private void free() { - Debug.Assert(Entry != null && HasEntryApplied); + Debug.Assert(entry != null && HasEntryApplied); - OnFree(Entry); + OnFree(entry); - Entry.LifetimeChanged -= setLifetimeFromEntry; - Entry = null; + entry.LifetimeChanged -= setLifetimeFromEntry; + entry = null; base.LifetimeStart = double.MinValue; base.LifetimeEnd = double.MaxValue; From 55859938b183c950b94625c552c66478a6ec3168 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 16:01:29 +0900 Subject: [PATCH 374/520] Use object initializer syntax for hit object application in tests --- .../Gameplay/TestSceneDrawableHitObject.cs | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index da0d57f9d1..0ce71696bd 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -44,11 +44,9 @@ namespace osu.Game.Tests.Gameplay { TestDrawableHitObject dho = null; TestLifetimeEntry entry = null; - AddStep("Create DHO", () => + AddStep("Create DHO", () => Child = dho = new TestDrawableHitObject { - dho = new TestDrawableHitObject(null); - dho.Apply(entry = new TestLifetimeEntry(new HitObject())); - Child = dho; + Entry = entry = new TestLifetimeEntry(new HitObject()) }); AddStep("KeepAlive = true", () => @@ -81,12 +79,10 @@ namespace osu.Game.Tests.Gameplay AddAssert("Lifetime is updated", () => entry.LifetimeStart == -TestLifetimeEntry.INITIAL_LIFETIME_OFFSET); TestDrawableHitObject dho = null; - AddStep("Create DHO", () => + AddStep("Create DHO", () => Child = dho = new TestDrawableHitObject { - dho = new TestDrawableHitObject(null); - dho.Apply(entry); - Child = dho; - dho.SetLifetimeStartOnApply = true; + Entry = entry, + SetLifetimeStartOnApply = true }); AddStep("ApplyDefaults", () => entry.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty())); AddAssert("Lifetime is correct", () => dho.LifetimeStart == TestDrawableHitObject.LIFETIME_ON_APPLY && entry.LifetimeStart == TestDrawableHitObject.LIFETIME_ON_APPLY); @@ -97,11 +93,9 @@ namespace osu.Game.Tests.Gameplay { TestDrawableHitObject dho = null; TestLifetimeEntry entry = null; - AddStep("Create DHO", () => + AddStep("Create DHO", () => Child = dho = new TestDrawableHitObject { - dho = new TestDrawableHitObject(null); - dho.Apply(entry = new TestLifetimeEntry(new HitObject())); - Child = dho; + Entry = entry = new TestLifetimeEntry(new HitObject()) }); AddStep("Set entry lifetime", () => @@ -135,7 +129,7 @@ namespace osu.Game.Tests.Gameplay public bool SetLifetimeStartOnApply; - public TestDrawableHitObject(HitObject hitObject) + public TestDrawableHitObject(HitObject hitObject = null) : base(hitObject) { } From a5c09454e61814c8bf3b05aff6f80ff4f1d29003 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 16:16:18 +0900 Subject: [PATCH 375/520] Remove unnecessary configuration --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 15fc8c130e..aeb71ccaf1 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -1,11 +1,9 @@ // 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.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -19,16 +17,6 @@ namespace osu.Game.Rulesets.Taiko.Mods public override string Description => @"Beats fade out before you hit them!"; public override double ScoreMultiplier => 1.06; - [SettingSource("Fade-out Time", "The bigger this multiplier is, the sooner the notes will start fading out")] - public BindableNumber FadeOutTimeMultiplier { get; } = new BindableDouble - { - MinValue = 0.5, - MaxValue = 1.5, - Default = 1.0, - Value = 1.0, - Precision = 0.01, - }; - /// /// In stable taiko, the hit position is 160, so the active playfield is essentially 160 pixels shorter /// than the actual screen width. The normalized playfield height is 480, so on a 4:3 screen the @@ -68,7 +56,7 @@ namespace osu.Game.Rulesets.Taiko.Mods return; } - var preempt = 10000 / MultiplierAt(hitObject.HitObject.StartTime) * FadeOutTimeMultiplier.Value; + var preempt = 10000 / MultiplierAt(hitObject.HitObject.StartTime); var start = hitObject.HitObject.StartTime - preempt * 0.6; var duration = preempt * 0.3; From 1632450918c9af1955f32fdc1a6a676550087440 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 16:17:14 +0900 Subject: [PATCH 376/520] Add comments --- osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs index 1038af7a48..fc34ba4c8b 100644 --- a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs +++ b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs @@ -16,6 +16,9 @@ namespace osu.Game.Rulesets.Catch.UI /// public class SkinnableCatcher : SkinnableDrawable { + /// + /// This is used by skin elements to determine which texture of the catcher is used. + /// [Cached] public readonly Bindable AnimationState = new Bindable(); From b087c95581e960f80fa26af370aceb15dadf251e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 16:14:42 +0900 Subject: [PATCH 377/520] Use a frozen clock for catcher trails --- osu.Game.Rulesets.Catch/UI/CatcherTrail.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs index f273ef23ac..80522ab36b 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; +using osu.Framework.Timing; using osuTK; namespace osu.Game.Rulesets.Catch.UI @@ -11,8 +12,6 @@ namespace osu.Game.Rulesets.Catch.UI /// A trail of the catcher. /// It also represents a hyper dash afterimage. /// - // TODO: Trails shouldn't be animated when the skin has an animated catcher. - // The animation should be frozen at the animation frame at the time of the trail generation. public class CatcherTrail : PoolableDrawable { public CatcherAnimationState AnimationState @@ -27,7 +26,12 @@ namespace osu.Game.Rulesets.Catch.UI Size = new Vector2(CatcherArea.CATCHER_SIZE); Origin = Anchor.TopCentre; Blending = BlendingParameters.Additive; - InternalChild = body = new SkinnableCatcher(); + InternalChild = body = new SkinnableCatcher + { + // Using a frozen clock because trails should not be animated when the skin has an animated catcher. + // TODO: The animation should be frozen at the animation frame at the time of the trail generation. + Clock = new FramedClock(new ManualClock()), + }; } protected override void FreeAfterUse() From 6d6604e2f0c8c65570b8d99661894ede7d7f3b38 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Jun 2021 16:19:46 +0900 Subject: [PATCH 378/520] Fix incorrect indentation I used this for the o!f example. --- .../Overlays/BeatmapListing/SearchPlayed.cs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs b/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs index 93c0644d45..f24cf46c2d 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs @@ -7,32 +7,32 @@ using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapListing { -[LocalisableEnum(typeof(SearchPlayedEnumLocalisationMapper))] -public enum SearchPlayed -{ - Any, - Played, - Unplayed -} - -public class SearchPlayedEnumLocalisationMapper : EnumLocalisationMapper -{ - public override LocalisableString Map(SearchPlayed value) + [LocalisableEnum(typeof(SearchPlayedEnumLocalisationMapper))] + public enum SearchPlayed { - switch (value) + Any, + Played, + Unplayed + } + + public class SearchPlayedEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(SearchPlayed value) { - case SearchPlayed.Any: - return BeatmapsStrings.PlayedAny; + switch (value) + { + case SearchPlayed.Any: + return BeatmapsStrings.PlayedAny; - case SearchPlayed.Played: - return BeatmapsStrings.PlayedPlayed; + case SearchPlayed.Played: + return BeatmapsStrings.PlayedPlayed; - case SearchPlayed.Unplayed: - return BeatmapsStrings.PlayedUnplayed; + case SearchPlayed.Unplayed: + return BeatmapsStrings.PlayedUnplayed; - default: - throw new ArgumentOutOfRangeException(nameof(value), value, null); + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } } } } -} From fafd936c93903fd773c1d671282e1747647b1c44 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Jun 2021 16:21:36 +0900 Subject: [PATCH 379/520] Localise "sort by" string in overlays --- osu.Game/Overlays/OverlaySortTabControl.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlaySortTabControl.cs b/osu.Game/Overlays/OverlaySortTabControl.cs index 0ebabd424f..15c43eeb01 100644 --- a/osu.Game/Overlays/OverlaySortTabControl.cs +++ b/osu.Game/Overlays/OverlaySortTabControl.cs @@ -18,6 +18,7 @@ using JetBrains.Annotations; using System; using osu.Framework.Extensions; using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays { @@ -54,7 +55,7 @@ namespace osu.Game.Overlays Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = @"Sort by" + Text = SortStrings.Default }, CreateControl().With(c => { From 5944c45f55314f962fc207dff1ae89448252dafa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 16:24:30 +0900 Subject: [PATCH 380/520] Specify types explicitly and don't handle non-nullable values with fallbacks --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 13 +++++++------ osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 5 +++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index aeb71ccaf1..f787a75c51 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -37,9 +37,10 @@ namespace osu.Game.Rulesets.Taiko.Mods protected double MultiplierAt(double position) { - var beatLength = controlPointInfo.TimingPointAt(position)?.BeatLength; - var speedMultiplier = controlPointInfo.DifficultyPointAt(position)?.SpeedMultiplier; - return originalSliderMultiplier * (speedMultiplier ?? 1.0) * TimingControlPoint.DEFAULT_BEAT_LENGTH / (beatLength ?? TimingControlPoint.DEFAULT_BEAT_LENGTH); + double beatLength = controlPointInfo.TimingPointAt(position).BeatLength; + double speedMultiplier = controlPointInfo.DifficultyPointAt(position).SpeedMultiplier; + + return originalSliderMultiplier * speedMultiplier * TimingControlPoint.DEFAULT_BEAT_LENGTH / beatLength; } protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) @@ -56,9 +57,9 @@ namespace osu.Game.Rulesets.Taiko.Mods return; } - var preempt = 10000 / MultiplierAt(hitObject.HitObject.StartTime); - var start = hitObject.HitObject.StartTime - preempt * 0.6; - var duration = preempt * 0.3; + double preempt = 10000 / MultiplierAt(hitObject.HitObject.StartTime); + double start = hitObject.HitObject.StartTime - preempt * 0.6; + double duration = preempt * 0.3; using (hitObject.BeginAbsoluteSequence(start)) { diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index d3a4b635f5..25d0843a71 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Lists; @@ -66,6 +67,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the difficulty control point at. /// The difficulty control point. + [NotNull] public DifficultyControlPoint DifficultyPointAt(double time) => binarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT); /// @@ -73,6 +75,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the effect control point at. /// The effect control point. + [NotNull] public EffectControlPoint EffectPointAt(double time) => binarySearchWithFallback(EffectPoints, time, EffectControlPoint.DEFAULT); /// @@ -80,6 +83,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the sound control point at. /// The sound control point. + [NotNull] public SampleControlPoint SamplePointAt(double time) => binarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT); /// @@ -87,6 +91,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the timing control point at. /// The timing control point. + [NotNull] public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : TimingControlPoint.DEFAULT); /// From 18343160cfee4f5a1d7dde5296d1b4502ce655df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 16:28:57 +0900 Subject: [PATCH 381/520] Reword comments slightly --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index f787a75c51..434069291c 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public override double ScoreMultiplier => 1.06; /// - /// In stable taiko, the hit position is 160, so the active playfield is essentially 160 pixels shorter + /// In osu-stable, the hit position is 160, so the active playfield is essentially 160 pixels shorter /// than the actual screen width. The normalized playfield height is 480, so on a 4:3 screen the /// playfield ratio of the active area up to the hit position will actually be (640 - 160) / 480 = 1. /// For custom resolutions/aspect ratios (x:y), the screen width given the normalized height becomes 480 * x / y instead, @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Mods private const double hd_sv_scale = (4.0 / 3.0 - 1.0 / 3.0) / (16.0 / 9.0 - 1.0 / 3.0); private double originalSliderMultiplier; + private ControlPointInfo controlPointInfo; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) @@ -79,12 +80,13 @@ namespace osu.Game.Rulesets.Taiko.Mods public void ApplyToDifficulty(BeatmapDifficulty difficulty) { + // needs to be read after all processing has been run (TaikoBeatmapConverter applies an adjustment which would otherwise be omitted). originalSliderMultiplier = difficulty.SliderMultiplier; - // the hidden mod on stable had an added playfield cover that essentially forced a 4:3 playfield ratio, by cutting off all objects past that size. - // lazer currently uses a playfield adjustment container which keeps a 16:9 ratio. - // therefore, increase the slider multiplier proportionally so that the notes stay on the screen for the same amount of time as on stable. - // note that this will means that the notes will scroll faster as they have a longer distance to travel on the screen in that same amount of time. + // osu-stable has an added playfield cover that essentially forces a 4:3 playfield ratio, by cutting off all objects past that size. + // This is not yet implemented; instead a playfield adjustment container is present which maintains a 16:9 ratio. + // For now, increase the slider multiplier proportionally so that the notes stay on the screen for the same amount of time as on stable. + // Note that this means that the notes will scroll faster as they have a longer distance to travel on the screen in that same amount of time. difficulty.SliderMultiplier /= hd_sv_scale; } From 98e0e89d3f9a2358312a56d23046759e43bb180b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 16:32:59 +0900 Subject: [PATCH 382/520] Nest adjustments for readability --- .../Mods/TaikoModHidden.cs | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 434069291c..0fd3625a93 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -49,28 +49,23 @@ namespace osu.Game.Rulesets.Taiko.Mods switch (hitObject) { case DrawableDrumRollTick _: - break; - case DrawableHit _: + double preempt = 10000 / MultiplierAt(hitObject.HitObject.StartTime); + double start = hitObject.HitObject.StartTime - preempt * 0.6; + double duration = preempt * 0.3; + + using (hitObject.BeginAbsoluteSequence(start)) + { + hitObject.FadeOut(duration); + + // DrawableHitObject sets LifetimeEnd to LatestTransformEndTime if it isn't manually changed. + // in order for the object to not be killed before its actual end time (as the latest transform ends earlier), set lifetime end explicitly. + hitObject.LifetimeEnd = state == ArmedState.Idle || !hitObject.AllJudged + ? hitObject.HitObject.GetEndTime() + hitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + : hitObject.HitStateUpdateTime; + } + break; - - default: - return; - } - - double preempt = 10000 / MultiplierAt(hitObject.HitObject.StartTime); - double start = hitObject.HitObject.StartTime - preempt * 0.6; - double duration = preempt * 0.3; - - using (hitObject.BeginAbsoluteSequence(start)) - { - hitObject.FadeOut(duration); - - // DrawableHitObject sets LifetimeEnd to LatestTransformEndTime if it isn't manually changed. - // in order for the object to not be killed before its actual end time (as the latest transform ends earlier), set lifetime end explicitly. - hitObject.LifetimeEnd = state == ArmedState.Idle || !hitObject.AllJudged - ? hitObject.HitObject.GetEndTime() + hitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) - : hitObject.HitStateUpdateTime; } } From 64bb1f381bdbc2647c1883fb386686fe7b0943cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Jun 2021 10:25:30 +0200 Subject: [PATCH 383/520] Add more languages to settings dropdown --- osu.Game/Localisation/Language.cs | 99 ++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index a3e845f229..0ad6b45104 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -10,7 +10,104 @@ namespace osu.Game.Localisation [Description(@"English")] en, + // TODO: Requires Arabic glyphs to be added to resources (and possibly also RTL support). + // [Description(@"اَلْعَرَبِيَّةُ")] + // ar, + + // TODO: Some accented glyphs are missing. Revisit when adding Inter. + // [Description(@"Беларуская мова")] + // be, + + [Description(@"Български")] + bg, + + [Description(@"Česky")] + cs, + + [Description(@"Dansk")] + da, + + [Description(@"Deutsch")] + de, + + // TODO: Some accented glyphs are missing. Revisit when adding Inter. + // [Description(@"Ελληνικά")] + // el, + + [Description(@"español")] + es, + + [Description(@"Suomi")] + fi, + + [Description(@"français")] + fr, + + [Description(@"Magyar")] + hu, + + [Description(@"Bahasa Indonesia")] + id, + + [Description(@"Italiano")] + it, + [Description(@"日本語")] - ja + ja, + + [Description(@"한국어")] + ko, + + [Description(@"Nederlands")] + nl, + + [Description(@"Norsk")] + no, + + [Description(@"polski")] + pl, + + [Description(@"Português")] + pt, + + [Description(@"Português (Brasil)")] + pt_br, + + [Description(@"Română")] + ro, + + [Description(@"Русский")] + ru, + + [Description(@"Slovenčina")] + sk, + + [Description(@"Svenska")] + se, + + [Description(@"ไทย")] + th, + + [Description(@"Tagalog")] + tl, + + [Description(@"Türkçe")] + tr, + + // TODO: Some accented glyphs are missing. Revisit when adding Inter. + // [Description(@"Українська мова")] + // uk, + + [Description(@"Tiếng Việt")] + vn, + + [Description(@"简体中文")] + zh, + + [Description(@"繁體中文(香港)")] + zh_hk, + + [Description(@"繁體中文(台灣)")] + zh_tw } } From d298e95df74eea7986245006bc9355dd9b024c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Jun 2021 10:25:50 +0200 Subject: [PATCH 384/520] Limit maximum height of settings enum dropdowns --- osu.Game/Overlays/Settings/SettingsEnumDropdown.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index c77d14632b..9987a0c607 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -19,6 +19,8 @@ namespace osu.Game.Overlays.Settings Margin = new MarginPadding { Top = 5 }; RelativeSizeAxes = Axes.X; } + + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200); } } } From ee5f4f18568e69d7263aba6381c0035874d4a232 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 17:27:43 +0900 Subject: [PATCH 385/520] Remove default (and make default "Gravity") --- osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index cd718a7b18..d01c036768 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs @@ -95,16 +95,15 @@ namespace osu.Game.Rulesets.Osu.Mods public enum AnimationStyle { - Default, + Gravity, + InOut1, + InOut2, Accelerate1, Accelerate2, Accelerate3, - Gravity, Decelerate1, Decelerate2, Decelerate3, - InOut1, - InOut2, } } } From 0c1023da3115e3741703f2e4eb719077ac2374ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 17:27:52 +0900 Subject: [PATCH 386/520] Simplify transform logic --- .../Mods/OsuModApproachDifferent.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index d01c036768..8f772e88ac 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs @@ -39,27 +39,23 @@ namespace osu.Game.Rulesets.Osu.Mods { drawables.ForEach(drawable => { - drawable.ApplyCustomUpdateState += (drawableHitObj, state) => + drawable.ApplyCustomUpdateState += (drawableObject, state) => { - if (!(drawableHitObj is DrawableHitCircle hitCircle)) return; + if (!(drawableObject is DrawableHitCircle drawableHitCircle)) return; - var obj = hitCircle.HitObject; + var hitCircle = drawableHitCircle.HitObject; - hitCircle.BeginAbsoluteSequence(obj.StartTime - obj.TimePreempt, true); - hitCircle.ApproachCircle.ScaleTo(Scale.Value); + drawableHitCircle.ApproachCircle.ClearTransforms(targetMember: nameof(Scale)); - hitCircle.ApproachCircle.FadeIn(Math.Min(obj.TimeFadeIn, obj.TimePreempt)); - - hitCircle.ApproachCircle.ScaleTo(1f, obj.TimePreempt, getEasing(Style.Value)); - - hitCircle.ApproachCircle.Expire(true); + using (drawableHitCircle.BeginAbsoluteSequence(hitCircle.StartTime - hitCircle.TimePreempt)) + drawableHitCircle.ApproachCircle.ScaleTo(Scale.Value).ScaleTo(1f, hitCircle.TimePreempt, getEasing(Style.Value)); }; }); } - private Easing getEasing(AnimationStyle approachEasing) + private Easing getEasing(AnimationStyle style) { - switch (approachEasing) + switch (style) { default: return Easing.None; From 7891ee4f32ea77b659c33a6d6b7f7e537eee7112 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 17:32:30 +0900 Subject: [PATCH 387/520] Change order of settings to make scrolling easier There's an issue with dropdown menus nested inside a scroll view being very frustrating to scroll to off-screen items. This works around that to some extent by giving the user more "parent-scrollable" space to mouse wheel or drag over. --- .../Mods/OsuModApproachDifferent.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index 8f772e88ac..3e638c4833 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.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 System; using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; @@ -22,19 +21,17 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle; - [SettingSource("Style", "Change the animation style of the approach circles.", 0)] - public Bindable Style { get; } = new Bindable(); - - [SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 1)] - public BindableFloat Scale { get; } = new BindableFloat + [SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)] + public BindableFloat Scale { get; } = new BindableFloat(4) { Precision = 0.1f, MinValue = 2, MaxValue = 10, - Default = 4, - Value = 4 }; + [SettingSource("Style", "Change the animation style of the approach circles.", 1)] + public Bindable Style { get; } = new Bindable(); + public void ApplyToDrawableHitObjects(IEnumerable drawables) { drawables.ForEach(drawable => From 521077b7148eb896792ed5c1a451074ab8957746 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 17:44:18 +0900 Subject: [PATCH 388/520] Make `getRulesetTransformedSkin` private --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8a27899e89..1953bd499b 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Skinning Ruleset = ruleset; Beatmap = beatmap; - InternalChild = new BeatmapSkinProvidingContainer(GetRulesetTransformedSkin(beatmapSkin)) + InternalChild = new BeatmapSkinProvidingContainer(getRulesetTransformedSkin(beatmapSkin)) { Child = Content = new Container { @@ -54,17 +54,17 @@ namespace osu.Game.Skinning { SkinSources.Clear(); - SkinSources.Add(GetRulesetTransformedSkin(skinManager.CurrentSkin.Value)); + SkinSources.Add(getRulesetTransformedSkin(skinManager.CurrentSkin.Value)); // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. if (skinManager.CurrentSkin.Value is LegacySkin && skinManager.CurrentSkin.Value != skinManager.DefaultLegacySkin) - SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultLegacySkin)); + SkinSources.Add(getRulesetTransformedSkin(skinManager.DefaultLegacySkin)); if (skinManager.CurrentSkin.Value != skinManager.DefaultSkin) - SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultSkin)); + SkinSources.Add(getRulesetTransformedSkin(skinManager.DefaultSkin)); } - protected ISkin GetRulesetTransformedSkin(ISkin skin) + private ISkin getRulesetTransformedSkin(ISkin skin) { if (skin == null) return null; From 2b0e6b6b5181c5d65f48fba003da63a034d52b65 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 17:44:21 +0900 Subject: [PATCH 389/520] Don't invoke "completed" action for test scene virtual track `MusicController` tries to play the next music when a track is completed. In test scenes, we want to keep the virtual track, not random songs. --- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index a4c78f24e3..98aad821ce 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -350,7 +350,7 @@ namespace osu.Game.Tests.Visual if (CurrentTime >= Length) { Stop(); - RaiseCompleted(); + // `RaiseCompleted` is not called here to prevent transitioning to the next song. } } } From cc5145a131fd25b1bab52c7fcddbac047681d4ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Jun 2021 11:10:03 +0200 Subject: [PATCH 390/520] Fix languages with a sub-language part not working properly --- osu.Game/Extensions/LanguageExtensions.cs | 33 +++++++++++++++++++ osu.Game/OsuGame.cs | 3 +- .../Sections/General/LanguageSettings.cs | 6 ++-- 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Extensions/LanguageExtensions.cs diff --git a/osu.Game/Extensions/LanguageExtensions.cs b/osu.Game/Extensions/LanguageExtensions.cs new file mode 100644 index 0000000000..b67e7fb6fc --- /dev/null +++ b/osu.Game/Extensions/LanguageExtensions.cs @@ -0,0 +1,33 @@ +// 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.Globalization; +using osu.Game.Localisation; + +namespace osu.Game.Extensions +{ + /// + /// Conversion utilities for the enum. + /// + public static class LanguageExtensions + { + /// + /// Returns the culture code of the that corresponds to the supplied . + /// + /// + /// This is required as enum member names are not allowed to contain hyphens. + /// + public static string ToCultureCode(this Language language) + => language.ToString().Replace("_", "-"); + + /// + /// Attempts to parse the supplied to a value. + /// + /// The code of the culture to parse. + /// The parsed . Valid only if the return value of the method is . + /// Whether the parsing succeeded. + public static bool TryParseCultureCode(string cultureCode, out Language language) + => Enum.TryParse(cultureCode.Replace("-", "_"), out language); + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 02e724a451..6eda4ff425 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -50,6 +50,7 @@ using osu.Game.Updater; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Localisation; using osu.Game.Skinning.Editor; @@ -580,7 +581,7 @@ namespace osu.Game foreach (var language in Enum.GetValues(typeof(Language)).OfType()) { - var cultureCode = language.ToString(); + var cultureCode = language.ToCultureCode(); Localisation.AddLanguage(cultureCode, new ResourceManagerLocalisationStore(cultureCode)); } diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs index c2767f61b4..dfcdb8e340 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs @@ -1,11 +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 System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Game.Extensions; using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.General @@ -35,11 +35,11 @@ namespace osu.Game.Overlays.Settings.Sections.General }, }; - if (!Enum.TryParse(frameworkLocale.Value, out var locale)) + if (!LanguageExtensions.TryParseCultureCode(frameworkLocale.Value, out var locale)) locale = Language.en; languageSelection.Current.Value = locale; - languageSelection.Current.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToString()); + languageSelection.Current.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToCultureCode()); } } } From e69bb67afe6e1ab615319844dfdf6ac3dfccd530 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 18:42:28 +0900 Subject: [PATCH 391/520] Add `IApplicableToDrawableHitObject` that is taking a single DHO Less cumbersome to implement than old version taking an enumerable. The implementation was always using `foreach` for the enumerable. The new interface is not used yet. --- .../Rulesets/Mods/IApplicableToDrawableHitObject.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs index 5630315770..a774ad5924 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Mods @@ -9,13 +10,19 @@ namespace osu.Game.Rulesets.Mods /// /// An interface for s that can be applied to s. /// - public interface IApplicableToDrawableHitObjects : IApplicableMod + public interface IApplicableToDrawableHitObject : IApplicableMod { /// - /// Applies this to a list of s. + /// Applies this to a . /// This will only be invoked with top-level s. Access if adjusting nested objects is necessary. /// - /// The list of s to apply to. + void ApplyToDrawableHitObject(DrawableHitObject drawable); + } + + public interface IApplicableToDrawableHitObjects : IApplicableToDrawableHitObject + { void ApplyToDrawableHitObjects(IEnumerable drawables); + + void IApplicableToDrawableHitObject.ApplyToDrawableHitObject(DrawableHitObject drawable) => ApplyToDrawableHitObjects(drawable.Yield()); } } From 67d8e0059f8ffa84de6f3811dc57bdfb988663ec Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 18:46:29 +0900 Subject: [PATCH 392/520] Use singular `IApplicableToDrawableHitObject` for consumers --- .../TestSceneDrawableHitObjects.cs | 4 ++-- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs | 4 ++-- osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs | 4 ++-- osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs | 4 ++-- osu.Game/Rulesets/UI/DrawableRuleset.cs | 7 +++++-- osu.Game/Rulesets/UI/Playfield.cs | 4 ++-- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 3e4995482d..fd6a9c7b7b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -174,8 +174,8 @@ namespace osu.Game.Rulesets.Catch.Tests private void addToPlayfield(DrawableCatchHitObject drawable) { - foreach (var mod in SelectedMods.Value.OfType()) - mod.ApplyToDrawableHitObjects(new[] { drawable }); + foreach (var mod in SelectedMods.Value.OfType()) + mod.ApplyToDrawableHitObject(drawable); drawableRuleset.Playfield.Add(drawable); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 58e46b6687..07acd5b6e2 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -75,8 +75,8 @@ namespace osu.Game.Rulesets.Osu.Tests var drawable = CreateDrawableHitCircle(circle, auto); - foreach (var mod in SelectedMods.Value.OfType()) - mod.ApplyToDrawableHitObjects(new[] { drawable }); + foreach (var mod in SelectedMods.Value.OfType()) + mod.ApplyToDrawableHitObject(drawable); return drawable; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index fc5fcf2358..81902c25af 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -335,8 +335,8 @@ namespace osu.Game.Rulesets.Osu.Tests var drawable = CreateDrawableSlider(slider); - foreach (var mod in SelectedMods.Value.OfType()) - mod.ApplyToDrawableHitObjects(new[] { drawable }); + foreach (var mod in SelectedMods.Value.OfType()) + mod.ApplyToDrawableHitObject(drawable); drawable.OnNewResult += onNewResult; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index b21b7a6f4a..2dea9837f3 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -85,8 +85,8 @@ namespace osu.Game.Rulesets.Osu.Tests Scale = new Vector2(0.75f) }; - foreach (var mod in SelectedMods.Value.OfType()) - mod.ApplyToDrawableHitObjects(new[] { drawableSpinner }); + foreach (var mod in SelectedMods.Value.OfType()) + mod.ApplyToDrawableHitObject(drawableSpinner); return drawableSpinner; } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 0ab8b94e3f..8dcc1ca164 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -199,8 +199,11 @@ namespace osu.Game.Rulesets.UI Playfield.PostProcess(); - foreach (var mod in Mods.OfType()) - mod.ApplyToDrawableHitObjects(Playfield.AllHitObjects); + foreach (var mod in Mods.OfType()) + { + foreach (var drawableHitObject in Playfield.AllHitObjects) + mod.ApplyToDrawableHitObject(drawableHitObject); + } } public override void RequestResume(Action continueResume) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index b154288dba..52aecb27de 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -356,8 +356,8 @@ namespace osu.Game.Rulesets.UI // This is done before Apply() so that the state is updated once when the hitobject is applied. if (mods != null) { - foreach (var m in mods.OfType()) - m.ApplyToDrawableHitObjects(dho.Yield()); + foreach (var m in mods.OfType()) + m.ApplyToDrawableHitObject(dho); } } From 379b84ba22b54d76033b16c58628bdce258a92b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Jun 2021 18:51:17 +0900 Subject: [PATCH 393/520] 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 490e43b5e6..1f60f02fb1 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8eeaad1127..68ffb87c6c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -34,7 +34,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index db442238ce..8aa79762fc 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From af80418ee8c7cd012beb819cbb96aa6ae7226566 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 18:52:01 +0900 Subject: [PATCH 394/520] Implement `IApplicableToDrawableHitObject` for mods A breaking change in `ModWithVisibilityAdjustment` if the method was overriden. --- .../Mods/OsuModBarrelRoll.cs | 22 ++++++--------- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 28 ++++++++----------- .../Mods/OsuModFlashlight.cs | 10 ++----- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 14 ++++------ .../Mods/ModWithVisibilityAdjustment.cs | 21 ++++++-------- 5 files changed, 38 insertions(+), 57 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index 9ae9653e9b..9e71f657ce 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.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 System.Collections.Generic; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; @@ -9,22 +8,19 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModBarrelRoll : ModBarrelRoll, IApplicableToDrawableHitObjects + public class OsuModBarrelRoll : ModBarrelRoll, IApplicableToDrawableHitObject { - public void ApplyToDrawableHitObjects(IEnumerable drawables) + public void ApplyToDrawableHitObject(DrawableHitObject d) { - foreach (var d in drawables) + d.OnUpdate += _ => { - d.OnUpdate += _ => + switch (d) { - switch (d) - { - case DrawableHitCircle circle: - circle.CirclePiece.Rotation = -CurrentRotation; - break; - } - }; - } + case DrawableHitCircle circle: + circle.CirclePiece.Rotation = -CurrentRotation; + break; + } + }; } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 77dea5b0dc..e04a30d06c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.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 System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; using osu.Game.Configuration; @@ -15,7 +14,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObjects, IApplicableToDrawableRuleset + public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset { [SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")] public Bindable NoSliderHeadAccuracy { get; } = new BindableBool(true); @@ -54,24 +53,21 @@ namespace osu.Game.Rulesets.Osu.Mods osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy(); } - public void ApplyToDrawableHitObjects(IEnumerable drawables) + public void ApplyToDrawableHitObject(DrawableHitObject obj) { - foreach (var obj in drawables) + switch (obj) { - switch (obj) - { - case DrawableSlider slider: - slider.Ball.InputTracksVisualSize = !FixedFollowCircleHitArea.Value; - break; + case DrawableSlider slider: + slider.Ball.InputTracksVisualSize = !FixedFollowCircleHitArea.Value; + break; - case DrawableSliderHead head: - head.TrackFollowCircle = !NoSliderHeadMovement.Value; - break; + case DrawableSliderHead head: + head.TrackFollowCircle = !NoSliderHeadMovement.Value; + break; - case DrawableSliderTail tail: - tail.SamplePlaysOnlyOnHit = !AlwaysPlayTailSample.Value; - break; - } + case DrawableSliderTail tail: + tail.SamplePlaysOnlyOnHit = !AlwaysPlayTailSample.Value; + break; } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 683b35f282..300a9d48aa 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; @@ -19,7 +17,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModFlashlight : ModFlashlight, IApplicableToDrawableHitObjects + public class OsuModFlashlight : ModFlashlight, IApplicableToDrawableHitObject { public override double ScoreMultiplier => 1.12; @@ -31,12 +29,10 @@ namespace osu.Game.Rulesets.Osu.Mods public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(); - public void ApplyToDrawableHitObjects(IEnumerable drawables) + public void ApplyToDrawableHitObject(DrawableHitObject drawable) { - foreach (var s in drawables.OfType()) - { + if (drawable is DrawableSlider s) s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; - } } public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index b12d735474..c7f4811701 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.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.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; @@ -13,7 +12,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModSpunOut : Mod, IApplicableToDrawableHitObjects + public class OsuModSpunOut : Mod, IApplicableToDrawableHitObject { public override string Name => "Spun Out"; public override string Acronym => "SO"; @@ -23,15 +22,12 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 0.9; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) }; - public void ApplyToDrawableHitObjects(IEnumerable drawables) + public void ApplyToDrawableHitObject(DrawableHitObject hitObject) { - foreach (var hitObject in drawables) + if (hitObject is DrawableSpinner spinner) { - if (hitObject is DrawableSpinner spinner) - { - spinner.HandleUserInput = false; - spinner.OnUpdate += onSpinnerUpdate; - } + spinner.HandleUserInput = false; + spinner.OnUpdate += onSpinnerUpdate; } } diff --git a/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs b/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs index 5b119b5e46..b58ee5ff36 100644 --- a/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs +++ b/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods /// A which applies visibility adjustments to s /// with an optional increased visibility adjustment depending on the user's "increase first object visibility" setting. /// - public abstract class ModWithVisibilityAdjustment : Mod, IReadFromConfig, IApplicableToBeatmap, IApplicableToDrawableHitObjects + public abstract class ModWithVisibilityAdjustment : Mod, IReadFromConfig, IApplicableToBeatmap, IApplicableToDrawableHitObject { /// /// The first adjustable object. @@ -73,19 +73,16 @@ namespace osu.Game.Rulesets.Mods } } - public virtual void ApplyToDrawableHitObjects(IEnumerable drawables) + public virtual void ApplyToDrawableHitObject(DrawableHitObject dho) { - foreach (var dho in drawables) + dho.ApplyCustomUpdateState += (o, state) => { - dho.ApplyCustomUpdateState += (o, state) => - { - // Increased visibility is applied to the entire first object, including all of its nested hitobjects. - if (IncreaseFirstObjectVisibility.Value && isObjectEqualToOrNestedIn(o.HitObject, FirstObject)) - ApplyIncreasedVisibilityState(o, state); - else - ApplyNormalVisibilityState(o, state); - }; - } + // Increased visibility is applied to the entire first object, including all of its nested hitobjects. + if (IncreaseFirstObjectVisibility.Value && isObjectEqualToOrNestedIn(o.HitObject, FirstObject)) + ApplyIncreasedVisibilityState(o, state); + else + ApplyNormalVisibilityState(o, state); + }; } /// From c59a3ce83f32dabedc0650912825a842723184d8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 18:52:16 +0900 Subject: [PATCH 395/520] Obsolete plural `IApplicableToDrawableHitObjects` --- osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs index a774ad5924..93055e733d 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.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; using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Objects.Drawables; @@ -19,6 +20,7 @@ namespace osu.Game.Rulesets.Mods void ApplyToDrawableHitObject(DrawableHitObject drawable); } + [Obsolete(@"Use the singular version IApplicableToDrawableHitObject instead.")] // Can be removed 20211216 public interface IApplicableToDrawableHitObjects : IApplicableToDrawableHitObject { void ApplyToDrawableHitObjects(IEnumerable drawables); From dc2e3ff89e7291054e39901715045ad8edfe4485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Jun 2021 12:01:58 +0200 Subject: [PATCH 396/520] Fix wrong language codes Confused them with the flags from the web-side source used. Tagalog (`tl`) still has no discernible effect, but that's because there are actually no localisation files in `osu-resources` for that language. Leave it be for now, as web has that entry, so it might mean that translations might be coming at some point in the future. --- osu.Game/Localisation/Language.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index 0ad6b45104..96bfde8596 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -83,7 +83,7 @@ namespace osu.Game.Localisation sk, [Description(@"Svenska")] - se, + sv, [Description(@"ไทย")] th, @@ -99,7 +99,7 @@ namespace osu.Game.Localisation // uk, [Description(@"Tiếng Việt")] - vn, + vi, [Description(@"简体中文")] zh, From 37babbde6a0db61c2d065d306066bd694252cdf7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Jun 2021 19:07:11 +0900 Subject: [PATCH 397/520] Simplify score filter row --- .../BeatmapSearchScoreFilterRow.cs | 35 +--------------- osu.Game/Scoring/ScoreRank.cs | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs index abfffe907f..b39934b56f 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs @@ -1,9 +1,9 @@ // 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.Linq; +using osu.Framework.Extensions; using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; @@ -33,38 +33,7 @@ namespace osu.Game.Overlays.BeatmapListing { } - protected override LocalisableString LabelFor(ScoreRank value) - { - switch (value) - { - case ScoreRank.XH: - return BeatmapsStrings.RankXH; - - case ScoreRank.X: - return BeatmapsStrings.RankX; - - case ScoreRank.SH: - return BeatmapsStrings.RankSH; - - case ScoreRank.S: - return BeatmapsStrings.RankS; - - case ScoreRank.A: - return BeatmapsStrings.RankA; - - case ScoreRank.B: - return BeatmapsStrings.RankB; - - case ScoreRank.C: - return BeatmapsStrings.RankC; - - case ScoreRank.D: - return BeatmapsStrings.RankD; - - default: - throw new ArgumentException("Unsupported value.", nameof(value)); - } - } + protected override LocalisableString LabelFor(ScoreRank value) => value.GetLocalisableDescription(); } } } diff --git a/osu.Game/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs index 696d493830..f3b4551ff8 100644 --- a/osu.Game/Scoring/ScoreRank.cs +++ b/osu.Game/Scoring/ScoreRank.cs @@ -1,10 +1,14 @@ // 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.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Scoring { + [LocalisableEnum(typeof(ScoreRankEnumLocalisationMapper))] public enum ScoreRank { [Description(@"D")] @@ -31,4 +35,40 @@ namespace osu.Game.Scoring [Description(@"SS+")] XH, } + + public class ScoreRankEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(ScoreRank value) + { + switch (value) + { + case ScoreRank.XH: + return BeatmapsStrings.RankXH; + + case ScoreRank.X: + return BeatmapsStrings.RankX; + + case ScoreRank.SH: + return BeatmapsStrings.RankSH; + + case ScoreRank.S: + return BeatmapsStrings.RankS; + + case ScoreRank.A: + return BeatmapsStrings.RankA; + + case ScoreRank.B: + return BeatmapsStrings.RankB; + + case ScoreRank.C: + return BeatmapsStrings.RankC; + + case ScoreRank.D: + return BeatmapsStrings.RankD; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } From 2155a4da0a3257b2e3b8e3488eabed564bcdd987 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Jun 2021 19:52:58 +0900 Subject: [PATCH 398/520] Fix intermittent HUD test failure --- osu.Game/Skinning/SkinnableTargetContainer.cs | 6 ++++++ osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index 1338462dd6..53b142f09a 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -18,6 +18,8 @@ namespace osu.Game.Skinning private readonly BindableList components = new BindableList(); + public bool ComponentsLoaded { get; private set; } + public SkinnableTargetContainer(SkinnableTarget target) { Target = target; @@ -30,6 +32,7 @@ namespace osu.Game.Skinning { ClearInternal(); components.Clear(); + ComponentsLoaded = false; content = CurrentSkin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetComponentsContainer; @@ -39,8 +42,11 @@ namespace osu.Game.Skinning { AddInternal(wrapper); components.AddRange(wrapper.Children.OfType()); + ComponentsLoaded = true; }); } + else + ComponentsLoaded = true; } /// diff --git a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs index b810bbf6ae..d74be70df8 100644 --- a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs +++ b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.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.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; @@ -47,6 +48,8 @@ namespace osu.Game.Tests.Visual LegacySkin.ResetDrawableTarget(t); t.Reload(); })); + + AddUntilStep("wait for components to load", () => this.ChildrenOfType().All(t => t.ComponentsLoaded)); } public class SkinProvidingPlayer : TestPlayer From a295421b64a81d1816a172040e8ffb9a31f46887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Jun 2021 13:27:02 +0200 Subject: [PATCH 399/520] Use language-specific lookup key for `Other` filter --- osu.Game/Overlays/BeatmapListing/SearchLanguage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs index 352383d576..fc176c305a 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs @@ -107,7 +107,7 @@ namespace osu.Game.Overlays.BeatmapListing return BeatmapsStrings.LanguagePolish; case SearchLanguage.Other: - return BeatmapsStrings.GenreOther; + return BeatmapsStrings.LanguageOther; default: throw new ArgumentOutOfRangeException(nameof(value), value, null); From 19f0e3d695c592ab882bd40e9309b7f7a393940b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Jun 2021 20:53:48 +0900 Subject: [PATCH 400/520] Add HighPerformanceSession --- osu.Game/OsuGame.cs | 5 ++ .../Performance/HighPerformanceSession.cs | 47 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 osu.Game/Performance/HighPerformanceSession.cs diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 6eda4ff425..0c4d035728 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -53,6 +53,7 @@ using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Localisation; +using osu.Game.Performance; using osu.Game.Skinning.Editor; namespace osu.Game @@ -488,6 +489,8 @@ namespace osu.Game protected virtual UpdateManager CreateUpdateManager() => new UpdateManager(); + protected virtual HighPerformanceSession CreateHighPerformanceSession() => new HighPerformanceSession(); + protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); #region Beatmap progression @@ -756,6 +759,8 @@ namespace osu.Game loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true); + loadComponentSingleFile(CreateHighPerformanceSession(), Add); + chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; Add(difficultyRecommender); diff --git a/osu.Game/Performance/HighPerformanceSession.cs b/osu.Game/Performance/HighPerformanceSession.cs new file mode 100644 index 0000000000..96e67669c5 --- /dev/null +++ b/osu.Game/Performance/HighPerformanceSession.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Runtime; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; + +namespace osu.Game.Performance +{ + public class HighPerformanceSession : Component + { + private readonly IBindable localUserPlaying = new Bindable(); + private GCLatencyMode originalGCMode; + + [BackgroundDependencyLoader] + private void load(OsuGame game) + { + localUserPlaying.BindTo(game.LocalUserPlaying); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + localUserPlaying.BindValueChanged(playing => + { + if (playing.NewValue) + EnableHighPerformanceSession(); + else + DisableHighPerformanceSession(); + }, true); + } + + protected virtual void EnableHighPerformanceSession() + { + originalGCMode = GCSettings.LatencyMode; + GCSettings.LatencyMode = GCLatencyMode.LowLatency; + } + + protected virtual void DisableHighPerformanceSession() + { + if (GCSettings.LatencyMode == GCLatencyMode.LowLatency) + GCSettings.LatencyMode = originalGCMode; + } + } +} From 90a13b8ed3dc203f2f585457fc8f2f66f563ece7 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 22:05:23 +0900 Subject: [PATCH 401/520] Use `IApplicableToDrawableHitObject` for `OsuModApproachDifferent` Replacing the obsolete interface. --- .../Mods/OsuModApproachDifferent.cs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index 3e638c4833..074fb7dbed 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs @@ -1,9 +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.Collections.Generic; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; @@ -13,7 +11,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObjects + public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject { public override string Name => "Approach Different"; public override string Acronym => "AD"; @@ -32,22 +30,19 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Style", "Change the animation style of the approach circles.", 1)] public Bindable Style { get; } = new Bindable(); - public void ApplyToDrawableHitObjects(IEnumerable drawables) + public void ApplyToDrawableHitObject(DrawableHitObject drawable) { - drawables.ForEach(drawable => + drawable.ApplyCustomUpdateState += (drawableObject, state) => { - drawable.ApplyCustomUpdateState += (drawableObject, state) => - { - if (!(drawableObject is DrawableHitCircle drawableHitCircle)) return; + if (!(drawableObject is DrawableHitCircle drawableHitCircle)) return; - var hitCircle = drawableHitCircle.HitObject; + var hitCircle = drawableHitCircle.HitObject; - drawableHitCircle.ApproachCircle.ClearTransforms(targetMember: nameof(Scale)); + drawableHitCircle.ApproachCircle.ClearTransforms(targetMember: nameof(Scale)); - using (drawableHitCircle.BeginAbsoluteSequence(hitCircle.StartTime - hitCircle.TimePreempt)) - drawableHitCircle.ApproachCircle.ScaleTo(Scale.Value).ScaleTo(1f, hitCircle.TimePreempt, getEasing(Style.Value)); - }; - }); + using (drawableHitCircle.BeginAbsoluteSequence(hitCircle.StartTime - hitCircle.TimePreempt)) + drawableHitCircle.ApproachCircle.ScaleTo(Scale.Value).ScaleTo(1f, hitCircle.TimePreempt, getEasing(Style.Value)); + }; } private Easing getEasing(AnimationStyle style) From 5ebf570ec4cdde860928b5c42da17695e3185d49 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Jun 2021 16:48:30 +0300 Subject: [PATCH 402/520] Revert `GetRulesetTransformedSkin` accessibility change This reverts commit 521077b7148eb896792ed5c1a451074ab8957746. Forgot to do it when I made this `protected`, but subclasses in test scenes require this. --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 10 +++++----- osu.Game/Tests/Visual/TestPlayer.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 1953bd499b..8a27899e89 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Skinning Ruleset = ruleset; Beatmap = beatmap; - InternalChild = new BeatmapSkinProvidingContainer(getRulesetTransformedSkin(beatmapSkin)) + InternalChild = new BeatmapSkinProvidingContainer(GetRulesetTransformedSkin(beatmapSkin)) { Child = Content = new Container { @@ -54,17 +54,17 @@ namespace osu.Game.Skinning { SkinSources.Clear(); - SkinSources.Add(getRulesetTransformedSkin(skinManager.CurrentSkin.Value)); + SkinSources.Add(GetRulesetTransformedSkin(skinManager.CurrentSkin.Value)); // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. if (skinManager.CurrentSkin.Value is LegacySkin && skinManager.CurrentSkin.Value != skinManager.DefaultLegacySkin) - SkinSources.Add(getRulesetTransformedSkin(skinManager.DefaultLegacySkin)); + SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultLegacySkin)); if (skinManager.CurrentSkin.Value != skinManager.DefaultSkin) - SkinSources.Add(getRulesetTransformedSkin(skinManager.DefaultSkin)); + SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultSkin)); } - private ISkin getRulesetTransformedSkin(ISkin skin) + protected ISkin GetRulesetTransformedSkin(ISkin skin) { if (skin == null) return null; diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index eecf8a2f6e..e1431b0658 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual base.UpdateSkins(); if (skin != null) - SkinSources.Insert(0, Ruleset.CreateLegacySkinProvider(skin, Beatmap)); + SkinSources.Insert(0, GetRulesetTransformedSkin(skin)); } } } From 4b926791b58344f6018941ac6463849bc742bdb5 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 16 Jun 2021 21:13:01 +0700 Subject: [PATCH 403/520] add inter font --- osu.Game/OsuGameBase.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9c3adba342..abf8fbc4fb 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -195,6 +195,15 @@ namespace osu.Game AddFont(Resources, @"Fonts/Torus-SemiBold"); AddFont(Resources, @"Fonts/Torus-Bold"); + AddFont(Resources, @"Fonts/Inter-Regular"); + AddFont(Resources, @"Fonts/Inter-RegularItalic"); + AddFont(Resources, @"Fonts/Inter-Light"); + AddFont(Resources, @"Fonts/Inter-LightItalic"); + AddFont(Resources, @"Fonts/Inter-SemiBold"); + AddFont(Resources, @"Fonts/Inter-SemiBoldItalic"); + AddFont(Resources, @"Fonts/Inter-Bold"); + AddFont(Resources, @"Fonts/Inter-BoldItalic"); + AddFont(Resources, @"Fonts/Noto-Basic"); AddFont(Resources, @"Fonts/Noto-Hangul"); AddFont(Resources, @"Fonts/Noto-CJK-Basic"); From 0e9ca3df3c5b8a54d9808711bacc926e1e832183 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 16 Jun 2021 21:14:48 +0700 Subject: [PATCH 404/520] add remaining non-rtl language --- osu.Game/Localisation/Language.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index 96bfde8596..65541feedf 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -14,9 +14,8 @@ namespace osu.Game.Localisation // [Description(@"اَلْعَرَبِيَّةُ")] // ar, - // TODO: Some accented glyphs are missing. Revisit when adding Inter. - // [Description(@"Беларуская мова")] - // be, + [Description(@"Беларуская мова")] + be, [Description(@"Български")] bg, @@ -30,9 +29,8 @@ namespace osu.Game.Localisation [Description(@"Deutsch")] de, - // TODO: Some accented glyphs are missing. Revisit when adding Inter. - // [Description(@"Ελληνικά")] - // el, + [Description(@"Ελληνικά")] + el, [Description(@"español")] es, @@ -94,9 +92,8 @@ namespace osu.Game.Localisation [Description(@"Türkçe")] tr, - // TODO: Some accented glyphs are missing. Revisit when adding Inter. - // [Description(@"Українська мова")] - // uk, + [Description(@"Українська мова")] + uk, [Description(@"Tiếng Việt")] vi, From 52ddf08532fd4e31d042ec8a06d226814b769c52 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Jun 2021 16:51:20 +0300 Subject: [PATCH 405/520] Consider not adding legacy skin transformers to non-legacy skins --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8a27899e89..13664897ac 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -12,7 +12,7 @@ namespace osu.Game.Skinning { /// /// A type of that provides access to the beatmap skin and user skin, - /// each transformed with the ruleset's own skin transformer individually. + /// with each legacy skin source transformed with the ruleset's legacy skin transformer. /// public class RulesetSkinProvidingContainer : SkinProvidingContainer { @@ -66,7 +66,7 @@ namespace osu.Game.Skinning protected ISkin GetRulesetTransformedSkin(ISkin skin) { - if (skin == null) + if (!(skin is LegacySkin)) return null; var rulesetTransformed = Ruleset.CreateLegacySkinProvider(skin, Beatmap); From 74ad6f9117f12c834a96a296c026824f002e3e17 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Jun 2021 17:24:30 +0300 Subject: [PATCH 406/520] Remove default skin from the ruleset skin sources That one doesn't need any changes to it, can be fetched from the `SkinManager` instead. --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 13664897ac..621e80ceb5 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -59,9 +59,6 @@ namespace osu.Game.Skinning // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. if (skinManager.CurrentSkin.Value is LegacySkin && skinManager.CurrentSkin.Value != skinManager.DefaultLegacySkin) SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultLegacySkin)); - - if (skinManager.CurrentSkin.Value != skinManager.DefaultSkin) - SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultSkin)); } protected ISkin GetRulesetTransformedSkin(ISkin skin) From 780388d174c63cb711d31377aaa521a4a3e29eb8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Jun 2021 03:48:25 +0300 Subject: [PATCH 407/520] Fix incorrect return value --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 621e80ceb5..f11acd981a 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Skinning protected ISkin GetRulesetTransformedSkin(ISkin skin) { if (!(skin is LegacySkin)) - return null; + return skin; var rulesetTransformed = Ruleset.CreateLegacySkinProvider(skin, Beatmap); if (rulesetTransformed != null) From 9dcd0bf311a7a869256cf3bd54965b9664b044fd Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 17 Jun 2021 10:07:52 +0900 Subject: [PATCH 408/520] Remove `IPlayfieldProvider` by caching `Playfield` --- .../Editor/ManiaSelectionBlueprintTestScene.cs | 5 ++--- .../Edit/Blueprints/ManiaSelectionBlueprint.cs | 5 +++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 11 +++++++++-- osu.Game/Rulesets/Edit/IPlayfieldProvider.cs | 12 ------------ 4 files changed, 14 insertions(+), 19 deletions(-) delete mode 100644 osu.Game/Rulesets/Edit/IPlayfieldProvider.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index e5abbc7246..124e1a35f9 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI; @@ -14,13 +13,13 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Editor { - [Cached(typeof(IPlayfieldProvider))] - public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene, IPlayfieldProvider + public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene { protected override Container Content => blueprints ?? base.Content; private readonly Container blueprints; + [Cached(typeof(Playfield))] public Playfield Playfield { get; } private readonly ScrollingTestContainer scrollingTestContainer; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 1b5cb03204..955336db57 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.Edit.Blueprints @@ -14,12 +15,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints where T : ManiaHitObject { [Resolved] - private IPlayfieldProvider playfieldProvider { get; set; } + private Playfield playfield { get; set; } [Resolved] private IScrollingInfo scrollingInfo { get; set; } - protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfieldProvider.Playfield).GetColumn(HitObject.Column).HitObjectContainer; + protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfield).GetColumn(HitObject.Column).HitObjectContainer; protected ManiaSelectionBlueprint(T hitObject) : base(hitObject) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 67c18b7e3c..a7005954b2 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -43,6 +43,9 @@ namespace osu.Game.Rulesets.Edit protected readonly Ruleset Ruleset; + // Provides `Playfield` + private DependencyContainer dependencies; + [Resolved] protected EditorClock EditorClock { get; private set; } @@ -69,6 +72,9 @@ namespace osu.Game.Rulesets.Edit Ruleset = ruleset; } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + [BackgroundDependencyLoader] private void load() { @@ -88,6 +94,8 @@ namespace osu.Game.Rulesets.Edit return; } + dependencies.CacheAs(Playfield); + const float toolbar_width = 200; InternalChildren = new Drawable[] @@ -415,8 +423,7 @@ namespace osu.Game.Rulesets.Edit /// [Cached(typeof(HitObjectComposer))] [Cached(typeof(IPositionSnapProvider))] - [Cached(typeof(IPlayfieldProvider))] - public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider, IPlayfieldProvider + public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider { protected HitObjectComposer() { diff --git a/osu.Game/Rulesets/Edit/IPlayfieldProvider.cs b/osu.Game/Rulesets/Edit/IPlayfieldProvider.cs deleted file mode 100644 index 4bfd4d2728..0000000000 --- a/osu.Game/Rulesets/Edit/IPlayfieldProvider.cs +++ /dev/null @@ -1,12 +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 osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Edit -{ - public interface IPlayfieldProvider - { - Playfield Playfield { get; } - } -} From a4f362dca64bcae5db9eab7e45c49d4ea887ae49 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 17 Jun 2021 10:11:58 +0900 Subject: [PATCH 409/520] Remove lifetime override of `DrawableManiaHitObject` The `AlwaysAlive` logic is now in all DHOs and it is now not necessary (and potentially conflicting). --- .../Drawables/DrawableManiaHitObject.cs | 57 ------------------- 1 file changed, 57 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 380ab35339..3ec68bfb56 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -85,63 +85,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables AccentColour.UnbindFrom(ParentHitObject.AccentColour); } - private double computedLifetimeStart; - - public override double LifetimeStart - { - get => base.LifetimeStart; - set - { - computedLifetimeStart = value; - - if (!AlwaysAlive) - base.LifetimeStart = value; - } - } - - private double computedLifetimeEnd; - - public override double LifetimeEnd - { - get => base.LifetimeEnd; - set - { - computedLifetimeEnd = value; - - if (!AlwaysAlive) - base.LifetimeEnd = value; - } - } - - private bool alwaysAlive; - - /// - /// Whether this should always remain alive. - /// - internal bool AlwaysAlive - { - get => alwaysAlive; - set - { - if (alwaysAlive == value) - return; - - alwaysAlive = value; - - if (value) - { - // Set the base lifetimes directly, to avoid mangling the computed lifetimes - base.LifetimeStart = double.MinValue; - base.LifetimeEnd = double.MaxValue; - } - else - { - LifetimeStart = computedLifetimeStart; - LifetimeEnd = computedLifetimeEnd; - } - } - } - protected virtual void OnDirectionChanged(ValueChangedEvent e) { Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; From e7954ecb606e53e88063a227168d2d3fc5623ab6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 17 Jun 2021 10:31:20 +0900 Subject: [PATCH 410/520] Use property instead of backing field consistently --- .../Objects/Pooling/PoolableDrawableWithLifetime.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index d5d1a7b55c..3ab85aa214 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -132,11 +132,11 @@ namespace osu.Game.Rulesets.Objects.Pooling private void free() { - Debug.Assert(entry != null && HasEntryApplied); + Debug.Assert(Entry != null && HasEntryApplied); - OnFree(entry); + OnFree(Entry); - entry.LifetimeChanged -= setLifetimeFromEntry; + Entry.LifetimeChanged -= setLifetimeFromEntry; entry = null; base.LifetimeStart = double.MinValue; base.LifetimeEnd = double.MaxValue; From 9d9892e99ed8290f0b5ad8d82e800efa84935473 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Jun 2021 04:38:55 +0300 Subject: [PATCH 411/520] Add legacy spinner approach circle implementation --- .../Skinning/Legacy/LegacySpinner.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 959589620b..259f16ca5e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected DrawableSpinner DrawableSpinner { get; private set; } + private Drawable approachCircle; + private Sprite spin; private Sprite clear; @@ -57,8 +59,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Depth = float.MinValue, RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Children = new[] { + approachCircle = getSpinnerApproachCircle(source), spin = new Sprite { Anchor = Anchor.TopCentre, @@ -101,6 +104,25 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy }.With(s => s.Font = s.Font.With(fixedWidth: false)), } }); + + static Drawable getSpinnerApproachCircle(ISkinSource source) + { + var spinnerProvider = source.FindProvider(s => + s.GetTexture("spinner-circle") != null || + s.GetTexture("spinner-top") != null); + + if (spinnerProvider is DefaultLegacySkin) + return Empty(); + + return new Sprite + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-approachcircle"), + Scale = new Vector2(SPRITE_SCALE * 1.86f), + Y = SPINNER_Y_CENTRE, + }; + } } private IBindable gainedBonus; @@ -175,6 +197,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy spmCounter.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out); } + using (BeginAbsoluteSequence(d.HitObject.StartTime)) + approachCircle.ScaleTo(SPRITE_SCALE * 1.86f).ScaleTo(SPRITE_SCALE * 0.1f, d.HitObject.Duration); + double spinFadeOutLength = Math.Min(400, d.HitObject.Duration); using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength, true)) From 249ae3141edb47ba78b1a994b53652015bfd3637 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Jun 2021 15:06:56 +0900 Subject: [PATCH 412/520] Add early/late tests for hit circles --- .../TestSceneHitCircle.cs | 28 ++++++++++++++----- .../TestSceneHitCircleComboChange.cs | 4 +-- .../TestSceneShaking.cs | 4 +-- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 58e46b6687..0fdf30d9f9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -37,6 +37,18 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Hit Small Stream", () => SetContents(_ => testStream(7, true))); } + [Test] + public void TestHittingEarly() + { + AddStep("Hit stream early", () => SetContents(_ => testStream(5, true, -150))); + } + + [Test] + public void TestHittingLate() + { + AddStep("Hit stream late", () => SetContents(_ => testStream(5, true, 150))); + } + private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) { var drawable = createSingle(circleSize, auto, timeOffset, positionOffset); @@ -46,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Tests return playfield; } - private Drawable testStream(float circleSize, bool auto = false) + private Drawable testStream(float circleSize, bool auto = false, double hitOffset = 0) { var playfield = new TestOsuPlayfield(); @@ -54,14 +66,14 @@ namespace osu.Game.Rulesets.Osu.Tests for (int i = 0; i <= 1000; i += 100) { - playfield.Add(createSingle(circleSize, auto, i, pos)); + playfield.Add(createSingle(circleSize, auto, i, pos, hitOffset)); pos.X += 50; } return playfield; } - private TestDrawableHitCircle createSingle(float circleSize, bool auto, double timeOffset, Vector2? positionOffset) + private TestDrawableHitCircle createSingle(float circleSize, bool auto, double timeOffset, Vector2? positionOffset, double hitOffset = 0) { positionOffset ??= Vector2.Zero; @@ -73,14 +85,14 @@ namespace osu.Game.Rulesets.Osu.Tests circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); - var drawable = CreateDrawableHitCircle(circle, auto); + var drawable = CreateDrawableHitCircle(circle, auto, hitOffset); foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); return drawable; } - protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) => new TestDrawableHitCircle(circle, auto) + protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto, double hitOffset = 0) => new TestDrawableHitCircle(circle, auto, hitOffset) { Depth = depthIndex++ }; @@ -88,18 +100,20 @@ namespace osu.Game.Rulesets.Osu.Tests protected class TestDrawableHitCircle : DrawableHitCircle { private readonly bool auto; + private readonly double hitOffset; - public TestDrawableHitCircle(HitCircle h, bool auto) + public TestDrawableHitCircle(HitCircle h, bool auto, double hitOffset) : base(h) { this.auto = auto; + this.hitOffset = hitOffset; } public void TriggerJudgement() => UpdateResult(true); protected override void CheckForResult(bool userTriggered, double timeOffset) { - if (auto && !userTriggered && timeOffset > 0) + if (auto && !userTriggered && timeOffset > hitOffset) { // force success ApplyResult(r => r.Type = HitResult.Great); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs index 5695462859..ff600172d2 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs @@ -16,11 +16,11 @@ namespace osu.Game.Rulesets.Osu.Tests Scheduler.AddDelayed(() => comboIndex.Value++, 250, true); } - protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) + protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto, double hitOffset = 0) { circle.ComboIndexBindable.BindTo(comboIndex); circle.IndexInCurrentComboBindable.BindTo(comboIndex); - return base.CreateDrawableHitCircle(circle, auto); + return base.CreateDrawableHitCircle(circle, auto, hitOffset); } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs index 7e973d0971..43900c9a5c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs @@ -26,9 +26,9 @@ namespace osu.Game.Rulesets.Osu.Tests return base.CreateBeatmapForSkinProvider(); } - protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) + protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto, double hitOffset = 0) { - var drawableHitObject = base.CreateDrawableHitCircle(circle, auto); + var drawableHitObject = base.CreateDrawableHitCircle(circle, auto, hitOffset); Debug.Assert(drawableHitObject.HitObject.HitWindows != null); From 6d0b3efa239db3013368e3df6e3a9c482397772c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Jun 2021 15:08:09 +0900 Subject: [PATCH 413/520] Reorganise existing tests into hits and misses --- .../TestSceneHitCircle.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 0fdf30d9f9..073e0540e5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -21,17 +21,11 @@ namespace osu.Game.Rulesets.Osu.Tests private int depthIndex; [Test] - public void TestVariousHitCircles() + public void TestHits() { - AddStep("Miss Big Single", () => SetContents(_ => testSingle(2))); - AddStep("Miss Medium Single", () => SetContents(_ => testSingle(5))); - AddStep("Miss Small Single", () => SetContents(_ => testSingle(7))); AddStep("Hit Big Single", () => SetContents(_ => testSingle(2, true))); AddStep("Hit Medium Single", () => SetContents(_ => testSingle(5, true))); AddStep("Hit Small Single", () => SetContents(_ => testSingle(7, true))); - AddStep("Miss Big Stream", () => SetContents(_ => testStream(2))); - AddStep("Miss Medium Stream", () => SetContents(_ => testStream(5))); - AddStep("Miss Small Stream", () => SetContents(_ => testStream(7))); AddStep("Hit Big Stream", () => SetContents(_ => testStream(2, true))); AddStep("Hit Medium Stream", () => SetContents(_ => testStream(5, true))); AddStep("Hit Small Stream", () => SetContents(_ => testStream(7, true))); @@ -43,6 +37,17 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Hit stream early", () => SetContents(_ => testStream(5, true, -150))); } + [Test] + public void TestMisses() + { + AddStep("Miss Big Single", () => SetContents(_ => testSingle(2))); + AddStep("Miss Medium Single", () => SetContents(_ => testSingle(5))); + AddStep("Miss Small Single", () => SetContents(_ => testSingle(7))); + AddStep("Miss Big Stream", () => SetContents(_ => testStream(2))); + AddStep("Miss Medium Stream", () => SetContents(_ => testStream(5))); + AddStep("Miss Small Stream", () => SetContents(_ => testStream(7))); + } + [Test] public void TestHittingLate() { From a46f730a6943cef55afc7e987ce0522367a6b6d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Jun 2021 15:08:22 +0900 Subject: [PATCH 414/520] Fix approach circle fade not running early on an early user hit Regressed in https://github.com/ppy/osu/pull/12153. Closes https://github.com/ppy/osu/issues/13531. --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 236af4b3f1..ca2e6578db 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -172,6 +172,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateStartTimeStateTransforms(); + // always fade out at the circle's start time (to match user expectations). ApproachCircle.FadeOut(50); } @@ -182,6 +183,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // todo: temporary / arbitrary, used for lifetime optimisation. this.Delay(800).FadeOut(); + // in the case of an early state change, the fade should be expedited to the current point in time. + if (HitStateUpdateTime < HitObject.StartTime) + ApproachCircle.FadeOut(50); + switch (state) { case ArmedState.Idle: From 8da0431fa076307fcaad9443304a98ef83bdb7cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Jun 2021 16:05:50 +0900 Subject: [PATCH 415/520] Improve code quality of `AuthorInfo` --- osu.Game/Overlays/BeatmapSet/AuthorInfo.cs | 70 +++++++++++----------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 1ffcf9722a..971b34fcc4 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -22,8 +23,8 @@ namespace osu.Game.Overlays.BeatmapSet { private const float height = 50; - private readonly UpdateableAvatar avatar; - private readonly FillFlowContainer fields; + private UpdateableAvatar avatar; + private FillFlowContainer fields; private BeatmapSetInfo beatmapSet; @@ -35,41 +36,12 @@ namespace osu.Game.Overlays.BeatmapSet if (value == beatmapSet) return; beatmapSet = value; - - updateDisplay(); + Scheduler.AddOnce(updateDisplay); } } - private void updateDisplay() - { - avatar.User = BeatmapSet?.Metadata.Author; - - fields.Clear(); - if (BeatmapSet == null) - return; - - var online = BeatmapSet.OnlineInfo; - - fields.Children = new Drawable[] - { - new Field("mapped by", BeatmapSet.Metadata.Author, OsuFont.GetFont(weight: FontWeight.Regular, italics: true)), - new Field("submitted", online.Submitted, OsuFont.GetFont(weight: FontWeight.Bold)) - { - Margin = new MarginPadding { Top = 5 }, - }, - }; - - if (online.Ranked.HasValue) - { - fields.Add(new Field(online.Status.ToString().ToLowerInvariant(), online.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); - } - else if (online.LastUpdated.HasValue) - { - fields.Add(new Field("last updated", online.LastUpdated.Value, OsuFont.GetFont(weight: FontWeight.Bold))); - } - } - - public AuthorInfo() + [BackgroundDependencyLoader] + private void load() { RelativeSizeAxes = Axes.X; Height = height; @@ -101,11 +73,37 @@ namespace osu.Game.Overlays.BeatmapSet Padding = new MarginPadding { Left = height + 5 }, }, }; + + Scheduler.AddOnce(updateDisplay); } - private void load() + private void updateDisplay() { - updateDisplay(); + avatar.User = BeatmapSet?.Metadata.Author; + + fields.Clear(); + if (BeatmapSet == null) + return; + + var online = BeatmapSet.OnlineInfo; + + fields.Children = new Drawable[] + { + new Field("mapped by", BeatmapSet.Metadata.Author, OsuFont.GetFont(weight: FontWeight.Regular, italics: true)), + new Field("submitted", online.Submitted, OsuFont.GetFont(weight: FontWeight.Bold)) + { + Margin = new MarginPadding { Top = 5 }, + }, + }; + + if (online.Ranked.HasValue) + { + fields.Add(new Field(online.Status.ToString().ToLowerInvariant(), online.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); + } + else if (online.LastUpdated.HasValue) + { + fields.Add(new Field("last updated", online.LastUpdated.Value, OsuFont.GetFont(weight: FontWeight.Bold))); + } } private class Field : FillFlowContainer From 9495f87f0462f5279f1ba43ff51fc3aee22db272 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Jun 2021 16:07:19 +0900 Subject: [PATCH 416/520] Remove redundant `NotNull` attributes in `nullable` classes --- osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 3 --- osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs | 2 -- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 3 +-- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 7d6c76bc2f..ee72df4c10 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using MessagePack; using osu.Game.Online.API; @@ -28,11 +27,9 @@ namespace osu.Game.Online.Multiplayer [Key(3)] public string Name { get; set; } = "Unnamed room"; - [NotNull] [Key(4)] public IEnumerable RequiredMods { get; set; } = Enumerable.Empty(); - [NotNull] [Key(5)] public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs index c654127b94..a49a8f083c 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using MessagePack; using Newtonsoft.Json; using osu.Game.Online.API; @@ -35,7 +34,6 @@ namespace osu.Game.Online.Multiplayer /// Any mods applicable only to the local user. /// [Key(3)] - [NotNull] public IEnumerable Mods { get; set; } = Enumerable.Empty(); [IgnoreMember] diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index bc8994bbe5..d3ee10dd23 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Game.Input.Handlers; using osu.Game.Replays; @@ -117,7 +116,7 @@ namespace osu.Game.Rulesets.Replays } } - protected virtual bool IsImportant([NotNull] TFrame frame) => false; + protected virtual bool IsImportant(TFrame frame) => false; /// /// Update the current frame based on an incoming time value. From ccba6b5ac29045f41b84bec840a947c3f677bd64 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Jun 2021 16:13:47 +0900 Subject: [PATCH 417/520] Fix test failures --- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 073e0540e5..cf8b510ab6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -114,11 +114,11 @@ namespace osu.Game.Rulesets.Osu.Tests this.hitOffset = hitOffset; } - public void TriggerJudgement() => UpdateResult(true); + public void TriggerJudgement() => Schedule(() => UpdateResult(true)); protected override void CheckForResult(bool userTriggered, double timeOffset) { - if (auto && !userTriggered && timeOffset > hitOffset) + if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current) != false) { // force success ApplyResult(r => r.Type = HitResult.Great); From d9cc1c227b0a3112ee0ad8683bfc1841ebef5d00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Jun 2021 16:25:55 +0900 Subject: [PATCH 418/520] Allow UpdateableAvatar to handle displaying username as tooltip --- .../Chat/Tabs/PrivateChannelTabItem.cs | 2 +- .../Profile/Header/TopHeaderContainer.cs | 3 +- .../Overlays/Toolbar/ToolbarUserButton.cs | 3 +- osu.Game/Users/Drawables/ClickableAvatar.cs | 41 +++++++++++++------ osu.Game/Users/Drawables/UpdateableAvatar.cs | 28 +++++++------ osu.Game/Users/ExtendedUserPanel.cs | 6 +-- 6 files changed, 48 insertions(+), 35 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 00f46b0035..7c82420e08 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Chat.Tabs Child = new DelayedLoadWrapper(avatar = new ClickableAvatar(value.Users.First()) { RelativeSizeAxes = Axes.Both, - OpenOnClick = { Value = false }, + OpenOnClick = false, }) { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index e0642d650c..419ae96738 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -58,12 +58,11 @@ namespace osu.Game.Overlays.Profile.Header Origin = Anchor.CentreLeft, Children = new Drawable[] { - avatar = new UpdateableAvatar + avatar = new UpdateableAvatar(openOnClick: false) { Size = new Vector2(avatar_size), Masking = true, CornerRadius = avatar_size * 0.25f, - OpenOnClick = { Value = false }, ShowGuestOnNull = false, }, new Container diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index db4e491d9a..165c095514 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -32,14 +32,13 @@ namespace osu.Game.Overlays.Toolbar Add(new OpaqueBackground { Depth = 1 }); - Flow.Add(avatar = new UpdateableAvatar + Flow.Add(avatar = new UpdateableAvatar(openOnClick: false) { Masking = true, Size = new Vector2(32), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, CornerRadius = 4, - OpenOnClick = { Value = false }, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 0fca9c7c9b..c3bf740108 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -2,7 +2,6 @@ // 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.Framework.Graphics.Textures; @@ -13,16 +12,32 @@ namespace osu.Game.Users.Drawables { public class ClickableAvatar : Container { + private const string default_tooltip_text = "view profile"; + /// /// Whether to open the user's profile when clicked. /// - public readonly BindableBool OpenOnClick = new BindableBool(true); + public bool OpenOnClick + { + set => clickableArea.Enabled.Value = value; + } + + /// + /// By default, the tooltip will show "view profile" as avatars are usually displayed next to a username. + /// Setting this to true exposes the username via tooltip for special cases where this is not true. + /// + public bool ShowUsernameTooltip + { + set => clickableArea.TooltipText = value ? (user?.Username ?? string.Empty) : default_tooltip_text; + } private readonly User user; [Resolved(CanBeNull = true)] private OsuGame game { get; set; } + private readonly ClickableArea clickableArea; + /// /// A clickable avatar for the specified user, with UI sounds included. /// If is true, clicking will open the user's profile. @@ -31,35 +46,35 @@ namespace osu.Game.Users.Drawables public ClickableAvatar(User user = null) { this.user = user; - } - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) - { - ClickableArea clickableArea; Add(clickableArea = new ClickableArea { RelativeSizeAxes = Axes.Both, Action = openProfile }); + } + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { LoadComponentAsync(new DrawableAvatar(user), clickableArea.Add); - - clickableArea.Enabled.BindTo(OpenOnClick); } private void openProfile() { - if (!OpenOnClick.Value) - return; - if (user?.Id > 1) game?.ShowUser(user.Id); } private class ClickableArea : OsuClickableContainer { - public override string TooltipText => Enabled.Value ? @"view profile" : null; + private string tooltip = default_tooltip_text; + + public override string TooltipText + { + get => Enabled.Value ? tooltip : null; + set => tooltip = value; + } protected override bool OnClick(ClickEvent e) { diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index 927e48cb56..df724404e9 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; @@ -45,33 +44,38 @@ namespace osu.Game.Users.Drawables protected override double LoadDelay => 200; - /// - /// Whether to show a default guest representation on null user (as opposed to nothing). - /// - public bool ShowGuestOnNull = true; + private readonly bool openOnClick; + private readonly bool showUsernameTooltip; + private readonly bool showGuestOnNull; /// - /// Whether to open the user's profile when clicked. + /// Construct a new UpdateableAvatar. /// - public readonly BindableBool OpenOnClick = new BindableBool(true); - - public UpdateableAvatar(User user = null) + /// The initial user to display. + /// Whether to open the user's profile when clicked. + /// Whether to show the username rather than "view profile" on the tooltip. + /// Whether to show a default guest representation on null user (as opposed to nothing). + public UpdateableAvatar(User user = null, bool openOnClick = true, bool showUsernameTooltip = false, bool showGuestOnNull = true) { + this.openOnClick = openOnClick; + this.showUsernameTooltip = showUsernameTooltip; + this.showGuestOnNull = showGuestOnNull; + User = user; } protected override Drawable CreateDrawable(User user) { - if (user == null && !ShowGuestOnNull) + if (user == null && !showGuestOnNull) return null; var avatar = new ClickableAvatar(user) { + OpenOnClick = openOnClick, + ShowUsernameTooltip = showUsernameTooltip, RelativeSizeAxes = Axes.Both, }; - avatar.OpenOnClick.BindTo(OpenOnClick); - return avatar; } } diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs index 2604815751..24317e6069 100644 --- a/osu.Game/Users/ExtendedUserPanel.cs +++ b/osu.Game/Users/ExtendedUserPanel.cs @@ -48,11 +48,7 @@ namespace osu.Game.Users statusIcon.FinishTransforms(); } - protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar - { - User = User, - OpenOnClick = { Value = false } - }; + protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar(User, false); protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.Country) { From 808b2baa41e47dd4751affb7c1d4ee80d92ea23a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Jun 2021 16:26:07 +0900 Subject: [PATCH 419/520] Consume new behaviour to fix `UserTile` discrepancy Closes https://github.com/ppy/osu/issues/13522. --- osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs index 9aceb39a27..e531ddb0ec 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Threading; using osu.Game.Users; @@ -91,7 +90,7 @@ namespace osu.Game.Screens.OnlinePlay.Components }); } - private class UserTile : CompositeDrawable, IHasTooltip + private class UserTile : CompositeDrawable { public User User { @@ -99,8 +98,6 @@ namespace osu.Game.Screens.OnlinePlay.Components set => avatar.User = value; } - public string TooltipText => User?.Username ?? string.Empty; - private readonly UpdateableAvatar avatar; public UserTile() @@ -116,7 +113,7 @@ namespace osu.Game.Screens.OnlinePlay.Components RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex(@"27252d"), }, - avatar = new UpdateableAvatar { RelativeSizeAxes = Axes.Both }, + avatar = new UpdateableAvatar(showUsernameTooltip: true) { RelativeSizeAxes = Axes.Both }, }; } } From a0e5301c9f6488138464146267771c4da385b87b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Jun 2021 16:33:43 +0900 Subject: [PATCH 420/520] Update usages of `showGuestOnNull` --- osu.Game/Overlays/BeatmapSet/AuthorInfo.cs | 3 +-- osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs | 3 +-- osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 1ffcf9722a..7c71e457d3 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -81,9 +81,8 @@ namespace osu.Game.Overlays.BeatmapSet AutoSizeAxes = Axes.Both, CornerRadius = 4, Masking = true, - Child = avatar = new UpdateableAvatar + Child = avatar = new UpdateableAvatar(showGuestOnNull: false) { - ShowGuestOnNull = false, Size = new Vector2(height), }, EdgeEffect = new EdgeEffectParameters diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 9111a0cfc7..736366fb5c 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }, } }, - avatar = new UpdateableAvatar + avatar = new UpdateableAvatar(showGuestOnNull: false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -75,7 +75,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Offset = new Vector2(0, 2), Radius = 1, }, - ShowGuestOnNull = false, }, new FillFlowContainer { diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 419ae96738..d751424367 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -58,12 +58,11 @@ namespace osu.Game.Overlays.Profile.Header Origin = Anchor.CentreLeft, Children = new Drawable[] { - avatar = new UpdateableAvatar(openOnClick: false) + avatar = new UpdateableAvatar(openOnClick: false, showGuestOnNull: false) { Size = new Vector2(avatar_size), Masking = true, CornerRadius = avatar_size * 0.25f, - ShowGuestOnNull = false, }, new Container { From 7cbebe6d11f4766420d6d0e48fd63cadec90dd52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Jun 2021 18:10:59 +0900 Subject: [PATCH 421/520] Rewrite xmldoc and inline comments for `PerformExit` --- osu.Game/Screens/Play/Player.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f9036780aa..ae8993cf06 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -512,19 +512,23 @@ namespace osu.Game.Screens.Play } /// - /// Exits the . + /// Attempts to complete a user request to exit gameplay. /// + /// + /// - This should only be called in response to a user interaction. Exiting is not guaranteed. + /// - This will interrupt any pending progression to the results screen, even if the transition has begun. + /// /// /// Whether the pause or fail dialog should be shown before performing an exit. - /// If true and a dialog is not yet displayed, the exit will be blocked the the relevant dialog will display instead. + /// If true and a dialog is not yet displayed, the exit will be blocked the relevant dialog will display instead. /// protected void PerformExit(bool showDialogFirst) { - // if a restart has been requested, cancel any pending completion (user has shown intent to restart). + // if an exit has been requested, cancel any pending completion (the user has showing intention to exit). completionProgressDelegate?.Cancel(); - // there is a chance that the exit was performed after the transition to results has started. - // we want to give the user what they want, so forcefully return to this screen (to proceed with the upwards exit process). + // there is a chance that an exit request occurs after the transition to results has already started. + // even in such a case, the user has shown intent, so forcefully return to this screen (to proceed with the upwards exit process). if (!this.IsCurrentScreen()) { ValidForResume = false; @@ -547,7 +551,7 @@ namespace osu.Game.Screens.Play return; } - // there's a chance the pausing is not supported in the current state, at which point immediate exit should be preferred. + // even if this call has requested a dialog, there is a chance the current player mode doesn't support pausing. if (pausingSupportedByCurrentState) { // in the case a dialog needs to be shown, attempt to pause and show it. @@ -563,6 +567,10 @@ namespace osu.Game.Screens.Play } } + // The actual exit is performed if + // - the pause / fail dialog was not requested + // - the pause / fail dialog was requested but is already displayed (user showing intention to exit). + // - the pause / fail dialog was requested but couldn't be displayed due to the type or state of this Player instance. this.Exit(); } From 246ab41cc68e361ea9ba71430dbfd8b776fcad54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Jun 2021 18:11:15 +0900 Subject: [PATCH 422/520] Remove special casing for user exit during storyboard outro --- .../Visual/Gameplay/TestSceneStoryboardWithOutro.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 5ef3eff856..3ed274690e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -66,12 +66,12 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestStoryboardExitToSkipOutro() + public void TestStoryboardExitDuringOutroStillExits() { CreateTest(null); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("exit via pause", () => Player.ExitViaPause()); - AddAssert("score shown", () => Player.IsScoreShown); + AddAssert("player exited", () => !Player.IsCurrentScreen() && Player.GetChildScreen() == null); } [TestCase(false)] diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ae8993cf06..b8d2b42992 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -559,12 +559,6 @@ namespace osu.Game.Screens.Play Pause(); return; } - - // if the score is ready for display but results screen has not been pushed yet (e.g. storyboard is still playing beyond gameplay), then transition to results screen instead of exiting. - if (prepareScoreForDisplayTask != null && completionProgressDelegate == null) - { - updateCompletionState(true); - } } // The actual exit is performed if From d03c6da60c9aad1477c5cc0fb6ffd127679cb86c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Jun 2021 19:13:34 +0900 Subject: [PATCH 423/520] Refactor and redocument `updateCompletionState()` and surrounding methods --- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 4 +- osu.Game/Screens/Play/Player.cs | 122 +++++++++++------- 2 files changed, 80 insertions(+), 46 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index a2ef715367..cc1fb0b321 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -54,9 +54,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists return new PlaylistsResultsScreen(score, RoomId.Value.Value, PlaylistItem, true); } - protected override void PrepareScoreForResults() + protected override void PrepareScoreForResults(Score score) { - base.PrepareScoreForResults(); + base.PrepareScoreForResults(score); Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore()); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b8d2b42992..6ef54db4ef 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -181,12 +181,6 @@ namespace osu.Game.Screens.Play DrawableRuleset.SetRecordTarget(Score); } - protected virtual void PrepareScoreForResults() - { - // perform one final population to ensure everything is up-to-date. - ScoreProcessor.PopulateScore(Score.ScoreInfo); - } - [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuConfigManager config, OsuGameBase game) { @@ -301,7 +295,7 @@ namespace osu.Game.Screens.Play DimmableStoryboard.HasStoryboardEnded.ValueChanged += storyboardEnded => { - if (storyboardEnded.NewValue && completionProgressDelegate == null) + if (storyboardEnded.NewValue && resultsDisplayDelegate == null) updateCompletionState(); }; @@ -525,7 +519,7 @@ namespace osu.Game.Screens.Play protected void PerformExit(bool showDialogFirst) { // if an exit has been requested, cancel any pending completion (the user has showing intention to exit). - completionProgressDelegate?.Cancel(); + resultsDisplayDelegate?.Cancel(); // there is a chance that an exit request occurs after the transition to results has already started. // even in such a case, the user has shown intent, so forcefully return to this screen (to proceed with the upwards exit process). @@ -628,7 +622,20 @@ namespace osu.Game.Screens.Play PerformExit(false); } - private ScheduledDelegate completionProgressDelegate; + /// + /// This delegate, when set, means the results screen has been queued to appear. + /// The display of the results screen may be delayed by any work being done in and . + /// + /// + /// Once set, this can *only* be cancelled by rewinding, ie. if ScoreProcessor.HasCompleted becomes false. + /// Even if the user requests an exit, it will forcefully proceed to the results screen (see special case in ). + /// + private ScheduledDelegate resultsDisplayDelegate; + + /// + /// A task which asynchronously prepares a completed score for display at results. + /// This may include performing net requests or importing the score into the database, generally to ensure things are in a sane state for the play session. + /// private Task prepareScoreForDisplayTask; /// @@ -638,57 +645,44 @@ namespace osu.Game.Screens.Play /// Thrown if this method is called more than once without changing state. private void updateCompletionState(bool skipStoryboardOutro = false) { - // screen may be in the exiting transition phase. + // If this player instance is already exiting upwards, don't attempt any kind of forward progress. if (!this.IsCurrentScreen()) return; + // Special case to handle rewinding post-completion. This is the only way already queued forward progress can be cancelled. + // TODO: Investigate whether this can be moved to a RewindablePlayer subclass or similar. + // Currently, even if this scenario is hit, prepareScoreForDisplay has already been queued (and potentially run). + // In scenarios where rewinding is possible (replay, spectating) this is a non-issue as no submission/import work is done, + // but it still doesn't feel right that this exists here. if (!ScoreProcessor.HasCompleted.Value) { - completionProgressDelegate?.Cancel(); - completionProgressDelegate = null; + resultsDisplayDelegate?.Cancel(); + resultsDisplayDelegate = null; + ValidForResume = true; skipOutroOverlay.Hide(); return; } - if (completionProgressDelegate != null) - throw new InvalidOperationException($"{nameof(updateCompletionState)} was fired more than once"); + if (resultsDisplayDelegate != null) + throw new InvalidOperationException(@$"{nameof(updateCompletionState)} should never be fired more than once."); // Only show the completion screen if the player hasn't failed if (HealthProcessor.HasFailed) return; + // Setting this early in the process means that even if something were to go wrong in the order of events following, there + // is no chance that a user could return to the (already completed) Player instance from a child screen. ValidForResume = false; - // ensure we are not writing to the replay any more, as we are about to consume and store the score. + // Ensure we are not writing to the replay any more, as we are about to consume and store the score. DrawableRuleset.SetRecordTarget(null); - if (!Configuration.ShowResults) return; + // Asynchronously run score preparation operations (database import, online submission etc.). + prepareScoreForDisplayTask ??= Task.Run(prepareScoreForResults); - prepareScoreForDisplayTask ??= Task.Run(async () => - { - PrepareScoreForResults(); - - try - { - await PrepareScoreForResultsAsync(Score).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.Error(ex, "Score preparation failed!"); - } - - try - { - await ImportScore(Score).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.Error(ex, "Score import failed!"); - } - - return Score.ScoreInfo; - }); + if (!Configuration.ShowResults) + return; if (skipStoryboardOutro) { @@ -708,7 +702,33 @@ namespace osu.Game.Screens.Play scheduleCompletion(); } - private void scheduleCompletion() => completionProgressDelegate = Schedule(() => + private async Task prepareScoreForResults() + { + // ReSharper disable once MethodHasAsyncOverload + PrepareScoreForResults(Score); + + try + { + await PrepareScoreForResultsAsync(Score).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.Error(ex, @"Score preparation failed!"); + } + + try + { + await ImportScore(Score).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.Error(ex, @"Score import failed!"); + } + + return Score.ScoreInfo; + } + + private void scheduleCompletion() => resultsDisplayDelegate = Schedule(() => { if (!prepareScoreForDisplayTask.IsCompleted) { @@ -917,10 +937,11 @@ namespace osu.Game.Screens.Play { screenSuspension?.Expire(); - if (completionProgressDelegate != null && !completionProgressDelegate.Cancelled && !completionProgressDelegate.Completed) + // if the results screen is prepared to be displayed, forcefully show it on an exit request. + // usually if a user has completed a play session they do want to see results. and if they don't they can hit the same key a second time. + if (resultsDisplayDelegate != null && !resultsDisplayDelegate.Cancelled && !resultsDisplayDelegate.Completed) { - // proceed to result screen if beatmap already finished playing - completionProgressDelegate.RunTask(); + resultsDisplayDelegate.RunTask(); return true; } @@ -981,6 +1002,19 @@ namespace osu.Game.Screens.Play score.ScoreInfo.OnlineScoreID = onlineScoreId; } + /// + /// Prepare the for display at results. + /// + /// + /// This is run synchronously before is run. + /// + /// The to prepare. + protected virtual void PrepareScoreForResults(Score score) + { + // perform one final population to ensure everything is up-to-date. + ScoreProcessor.PopulateScore(score.ScoreInfo); + } + /// /// Prepare the for display at results. /// From 561dbea9e1c2ff77df25eee5499b62684b4ffa65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 17 Jun 2021 15:26:50 +0200 Subject: [PATCH 424/520] Use xmldoc-specific syntax for nicer formatting --- osu.Game/Screens/Play/Player.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b8d2b42992..df3348c9d5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -515,12 +515,14 @@ namespace osu.Game.Screens.Play /// Attempts to complete a user request to exit gameplay. /// /// - /// - This should only be called in response to a user interaction. Exiting is not guaranteed. - /// - This will interrupt any pending progression to the results screen, even if the transition has begun. + /// + /// This should only be called in response to a user interaction. Exiting is not guaranteed. + /// This will interrupt any pending progression to the results screen, even if the transition has begun. + /// /// /// /// Whether the pause or fail dialog should be shown before performing an exit. - /// If true and a dialog is not yet displayed, the exit will be blocked the relevant dialog will display instead. + /// If and a dialog is not yet displayed, the exit will be blocked the relevant dialog will display instead. /// protected void PerformExit(bool showDialogFirst) { From 10b0d066beb7e9e6fae56f10c7253739c6cecc86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 17 Jun 2021 16:04:58 +0200 Subject: [PATCH 425/520] Reword comments slightly --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index df3348c9d5..5ffe2bac49 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -522,11 +522,11 @@ namespace osu.Game.Screens.Play /// /// /// Whether the pause or fail dialog should be shown before performing an exit. - /// If and a dialog is not yet displayed, the exit will be blocked the relevant dialog will display instead. + /// If and a dialog is not yet displayed, the exit will be blocked and the relevant dialog will display instead. /// protected void PerformExit(bool showDialogFirst) { - // if an exit has been requested, cancel any pending completion (the user has showing intention to exit). + // if an exit has been requested, cancel any pending completion (the user has shown intention to exit). completionProgressDelegate?.Cancel(); // there is a chance that an exit request occurs after the transition to results has already started. From 9facfe89640fe31a14a7d119c84c153f37e70507 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 01:07:54 +0900 Subject: [PATCH 426/520] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index fe5df0428e..d82450255d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -629,7 +629,7 @@ namespace osu.Game.Screens.Play /// The display of the results screen may be delayed by any work being done in and . /// /// - /// Once set, this can *only* be cancelled by rewinding, ie. if ScoreProcessor.HasCompleted becomes false. + /// Once set, this can *only* be cancelled by rewinding, ie. if ScoreProcessor.HasCompleted becomes . /// Even if the user requests an exit, it will forcefully proceed to the results screen (see special case in ). /// private ScheduledDelegate resultsDisplayDelegate; @@ -647,7 +647,7 @@ namespace osu.Game.Screens.Play /// Thrown if this method is called more than once without changing state. private void updateCompletionState(bool skipStoryboardOutro = false) { - // If this player instance is already exiting upwards, don't attempt any kind of forward progress. + // If this player instance is in the middle of an exit, don't attempt any kind of state update. if (!this.IsCurrentScreen()) return; From 3a1444e75d5e1455d72b6cd47100aa57ed7a9b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 17 Jun 2021 19:02:56 +0200 Subject: [PATCH 427/520] Fix malformed xmldoc tag Oops. Made a typo in the PR suggestion. --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d82450255d..aedb8d667e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -629,7 +629,7 @@ namespace osu.Game.Screens.Play /// The display of the results screen may be delayed by any work being done in and . /// /// - /// Once set, this can *only* be cancelled by rewinding, ie. if ScoreProcessor.HasCompleted becomes . + /// Once set, this can *only* be cancelled by rewinding, ie. if ScoreProcessor.HasCompleted becomes . /// Even if the user requests an exit, it will forcefully proceed to the results screen (see special case in ). /// private ScheduledDelegate resultsDisplayDelegate; From f282326f9a94faf41df5537ab4245dc66172f206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 17 Jun 2021 19:04:52 +0200 Subject: [PATCH 428/520] Move score preparations back below `ShowResults` check --- osu.Game/Screens/Play/Player.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index aedb8d667e..9fe12c58de 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -680,12 +680,12 @@ namespace osu.Game.Screens.Play // Ensure we are not writing to the replay any more, as we are about to consume and store the score. DrawableRuleset.SetRecordTarget(null); - // Asynchronously run score preparation operations (database import, online submission etc.). - prepareScoreForDisplayTask ??= Task.Run(prepareScoreForResults); - if (!Configuration.ShowResults) return; + // Asynchronously run score preparation operations (database import, online submission etc.). + prepareScoreForDisplayTask ??= Task.Run(prepareScoreForResults); + if (skipStoryboardOutro) { scheduleCompletion(); From c9458fd9cec36e9d9407f4210fa39bf9bbef4ab1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Jun 2021 04:57:29 +0300 Subject: [PATCH 429/520] Hide spinner approach circle in "Hidden" mod --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 3 +++ osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 7 +++++++ osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 2752feb0a1..bec4c62e0f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -110,6 +110,9 @@ namespace osu.Game.Rulesets.Osu.Mods // hide elements we don't care about. // todo: hide background + using (spinner.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt)) + spinner.HideApproachCircle(); + using (spinner.BeginAbsoluteSequence(fadeStartTime)) spinner.FadeOut(fadeDuration); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 19cee61f26..942cc52c50 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; + internal readonly Bindable ApproachCircleVisibility = new Bindable(Visibility.Visible); + /// /// The amount of bonus score gained from spinning after the required number of spins, for display purposes. /// @@ -285,6 +287,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables updateBonusScore(); } + /// + /// Hides the spinner's approach circle if it has one. + /// + public void HideApproachCircle() => this.TransformBindableTo(ApproachCircleVisibility, Visibility.Hidden); + private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult; private int wholeSpins; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 259f16ca5e..5b0c2d405b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -125,6 +125,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } } + private IBindable approachCircleVisibility; private IBindable gainedBonus; private IBindable spinsPerMinute; @@ -134,6 +135,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { base.LoadComplete(); + approachCircleVisibility = DrawableSpinner.ApproachCircleVisibility.GetBoundCopy(); + approachCircleVisibility.BindValueChanged(v => + { + approachCircle.Alpha = v.NewValue == Visibility.Hidden ? 0 : 1; + }, true); + gainedBonus = DrawableSpinner.GainedBonus.GetBoundCopy(); gainedBonus.BindValueChanged(bonus => { From 97cd1217a47985b1dd9052171905e5106a96a5fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Jun 2021 13:06:13 +0900 Subject: [PATCH 430/520] Move IApplicableToDrawableHitObjects to its own file --- .../Mods/IApplicableToDrawableHitObject.cs | 11 ----------- .../Mods/IApplicableToDrawableHitObjects.cs | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs index 93055e733d..c8a9ff2f9a 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs @@ -1,9 +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 System; -using System.Collections.Generic; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Mods @@ -19,12 +16,4 @@ namespace osu.Game.Rulesets.Mods /// void ApplyToDrawableHitObject(DrawableHitObject drawable); } - - [Obsolete(@"Use the singular version IApplicableToDrawableHitObject instead.")] // Can be removed 20211216 - public interface IApplicableToDrawableHitObjects : IApplicableToDrawableHitObject - { - void ApplyToDrawableHitObjects(IEnumerable drawables); - - void IApplicableToDrawableHitObject.ApplyToDrawableHitObject(DrawableHitObject drawable) => ApplyToDrawableHitObjects(drawable.Yield()); - } } diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs new file mode 100644 index 0000000000..7f926dd8b8 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs @@ -0,0 +1,18 @@ +// 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 osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mods +{ + [Obsolete(@"Use the singular version IApplicableToDrawableHitObject instead.")] // Can be removed 20211216 + public interface IApplicableToDrawableHitObjects : IApplicableToDrawableHitObject + { + void ApplyToDrawableHitObjects(IEnumerable drawables); + + void IApplicableToDrawableHitObject.ApplyToDrawableHitObject(DrawableHitObject drawable) => ApplyToDrawableHitObjects(drawable.Yield()); + } +} From 5933e0d2d99a30ebc57b83a8dad81934746eabc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 13:17:32 +0900 Subject: [PATCH 431/520] Change `CheckCompatibleSet` to never deselect the current candidat when checking incompatibility --- osu.Game.Tests/Mods/ModUtilsTest.cs | 30 +++++++++++++++++++++++++++-- osu.Game/Utils/ModUtils.cs | 3 +++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 7384471c41..9f27289d7e 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -21,6 +21,14 @@ namespace osu.Game.Tests.Mods Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object })); } + [Test] + public void TestModIsCompatibleByItselfWithIncompatibleInterface() + { + var mod = new Mock(); + mod.Setup(m => m.IncompatibleMods).Returns(new[] { typeof(IModCompatibilitySpecification) }); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object })); + } + [Test] public void TestIncompatibleThroughTopLevel() { @@ -34,6 +42,20 @@ namespace osu.Game.Tests.Mods Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.False); } + [Test] + public void TestIncompatibleThroughInterface() + { + var mod1 = new Mock(); + var mod2 = new Mock(); + + mod1.Setup(m => m.IncompatibleMods).Returns(new[] { typeof(IModCompatibilitySpecification) }); + mod2.Setup(m => m.IncompatibleMods).Returns(new[] { typeof(IModCompatibilitySpecification) }); + + // Test both orderings. + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, mod2.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.False); + } + [Test] public void TestMultiModIncompatibleWithTopLevel() { @@ -149,11 +171,15 @@ namespace osu.Game.Tests.Mods Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); } - public abstract class CustomMod1 : Mod + public abstract class CustomMod1 : Mod, IModCompatibilitySpecification { } - public abstract class CustomMod2 : Mod + public abstract class CustomMod2 : Mod, IModCompatibilitySpecification + { + } + + public interface IModCompatibilitySpecification { } } diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 1c3558fc90..98766cb844 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -60,6 +60,9 @@ namespace osu.Game.Utils { foreach (var invalid in combination.Where(m => type.IsInstanceOfType(m))) { + if (invalid == mod) + continue; + invalidMods ??= new List(); invalidMods.Add(invalid); } From 4de27429bc0dc65f988c8b1e67941464ce840bb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 13:17:55 +0900 Subject: [PATCH 432/520] Change `ModSelectOverlay` to never deselect the user triggered selection --- osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs | 2 +- osu.Game/Overlays/Mods/ModSection.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs b/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs index 78cd9bdae5..db76581108 100644 --- a/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Mods base.OnModSelected(mod); foreach (var section in ModSectionsContainer.Children) - section.DeselectTypes(mod.IncompatibleMods, true); + section.DeselectTypes(mod.IncompatibleMods, true, mod); } } } diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index aa8a5efd39..6e289dc8aa 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -159,12 +159,16 @@ namespace osu.Game.Overlays.Mods /// /// The types of s which should be deselected. /// Whether the deselection should happen immediately. Should only be used when required to ensure correct selection flow. - public void DeselectTypes(IEnumerable modTypes, bool immediate = false) + /// If this deselection is triggered by a user selection, this should contain the newly selected type. This type will never be deselected, even if it matches one provided in . + public void DeselectTypes(IEnumerable modTypes, bool immediate = false, Mod newSelection = null) { foreach (var button in Buttons) { if (button.SelectedMod == null) continue; + if (button.SelectedMod == newSelection) + continue; + foreach (var type in modTypes) { if (type.IsInstanceOfType(button.SelectedMod)) From 860626152aedc57dc941031e60067ae63c253181 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 12:54:40 +0900 Subject: [PATCH 433/520] Mark all mods which adjust approach circle as incompatible with each other Closes https://github.com/ppy/osu/issues/13543. --- osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs | 12 ++++++++++++ .../Mods/OsuModApproachDifferent.cs | 5 ++++- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 4 ++-- 6 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs diff --git a/osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs new file mode 100644 index 0000000000..60a5825241 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Osu.Mods +{ + /// + /// Any mod which affects the animation or visibility of approach circles. Should be used for incompatibility purposes. + /// + public interface IMutateApproachCircles + { + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index 074fb7dbed..526e29ad53 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.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; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; @@ -11,7 +12,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject + public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject, IMutateApproachCircles { public override string Name => "Approach Different"; public override string Acronym => "AD"; @@ -19,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle; + public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; + [SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)] public BindableFloat Scale { get; } = new BindableFloat(4) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 2752feb0a1..a7c79aa2a0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -14,12 +14,12 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModHidden : ModHidden + public class OsuModHidden : ModHidden, IMutateApproachCircles { public override string Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => 1.06; - public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable), typeof(OsuModSpinIn) }; + public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index d1be162f73..6dfabed0df 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Adjusts the size of hit objects during their fade in animation. /// - public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment + public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment, IMutateApproachCircles { public override ModType Type => ModType.Fun; @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected virtual float EndScale => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn), typeof(OsuModTraceable) }; + public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 96ba58da23..d3ca2973f0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -12,7 +12,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModSpinIn : ModWithVisibilityAdjustment + public class OsuModSpinIn : ModWithVisibilityAdjustment, IMutateApproachCircles { public override string Name => "Spin In"; public override string Acronym => "SI"; @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; // todo: this mod should be able to be compatible with hidden with a bit of further implementation. - public override Type[] IncompatibleMods => new[] { typeof(OsuModObjectScaleTween), typeof(OsuModHidden), typeof(OsuModTraceable) }; + public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; private const int rotate_offset = 360; private const float rotate_starting_width = 2; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 4b0939db16..84263221a7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Skinning.Default; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModTraceable : ModWithVisibilityAdjustment + public class OsuModTraceable : ModWithVisibilityAdjustment, IMutateApproachCircles { public override string Name => "Traceable"; public override string Acronym => "TC"; @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween) }; + public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { From 5cf2ac78fcc1790b2556d11336c10496c7a94da8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Jun 2021 15:40:35 +0900 Subject: [PATCH 434/520] Adjust font namespaces --- osu.Game/OsuGameBase.cs | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index abf8fbc4fb..05a53452f7 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -190,29 +190,29 @@ namespace osu.Game AddFont(Resources, @"Fonts/osuFont"); - AddFont(Resources, @"Fonts/Torus-Regular"); - AddFont(Resources, @"Fonts/Torus-Light"); - AddFont(Resources, @"Fonts/Torus-SemiBold"); - AddFont(Resources, @"Fonts/Torus-Bold"); + AddFont(Resources, @"Fonts/Torus/Torus-Regular"); + AddFont(Resources, @"Fonts/Torus/Torus-Light"); + AddFont(Resources, @"Fonts/Torus/Torus-SemiBold"); + AddFont(Resources, @"Fonts/Torus/Torus-Bold"); - AddFont(Resources, @"Fonts/Inter-Regular"); - AddFont(Resources, @"Fonts/Inter-RegularItalic"); - AddFont(Resources, @"Fonts/Inter-Light"); - AddFont(Resources, @"Fonts/Inter-LightItalic"); - AddFont(Resources, @"Fonts/Inter-SemiBold"); - AddFont(Resources, @"Fonts/Inter-SemiBoldItalic"); - AddFont(Resources, @"Fonts/Inter-Bold"); - AddFont(Resources, @"Fonts/Inter-BoldItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-Regular"); + AddFont(Resources, @"Fonts/Inter/Inter-RegularItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-Light"); + AddFont(Resources, @"Fonts/Inter/Inter-LightItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-SemiBold"); + AddFont(Resources, @"Fonts/Inter/Inter-SemiBoldItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-Bold"); + AddFont(Resources, @"Fonts/Inter/Inter-BoldItalic"); - AddFont(Resources, @"Fonts/Noto-Basic"); - AddFont(Resources, @"Fonts/Noto-Hangul"); - AddFont(Resources, @"Fonts/Noto-CJK-Basic"); - AddFont(Resources, @"Fonts/Noto-CJK-Compatibility"); - AddFont(Resources, @"Fonts/Noto-Thai"); + AddFont(Resources, @"Fonts/Noto/Noto-Basic"); + AddFont(Resources, @"Fonts/Noto/Noto-Hangul"); + AddFont(Resources, @"Fonts/Noto/Noto-CJK-Basic"); + AddFont(Resources, @"Fonts/Noto/Noto-CJK-Compatibility"); + AddFont(Resources, @"Fonts/Noto/Noto-Thai"); - AddFont(Resources, @"Fonts/Venera-Light"); - AddFont(Resources, @"Fonts/Venera-Bold"); - AddFont(Resources, @"Fonts/Venera-Black"); + AddFont(Resources, @"Fonts/Venera/Venera-Light"); + AddFont(Resources, @"Fonts/Venera/Venera-Bold"); + AddFont(Resources, @"Fonts/Venera/Venera-Black"); Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY; From 2bf855fcca95d02d5a52746699a824ca9cebd5de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 15:45:12 +0900 Subject: [PATCH 435/520] Move all storyboard outro skip logic out of `updateCompletionState` This method should only be called to trigger the score completion portion of player progression. The storyboard skip/end logic is now handled separately in `progressToResults`. --- osu.Game/Screens/Play/Player.cs | 71 +++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9fe12c58de..c8ba554c5f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -295,12 +295,12 @@ namespace osu.Game.Screens.Play DimmableStoryboard.HasStoryboardEnded.ValueChanged += storyboardEnded => { - if (storyboardEnded.NewValue && resultsDisplayDelegate == null) - updateCompletionState(); + if (storyboardEnded.NewValue) + progressToResults(true); }; // Bind the judgement processors to ourselves - ScoreProcessor.HasCompleted.BindValueChanged(_ => updateCompletionState()); + ScoreProcessor.HasCompleted.BindValueChanged(scoreCompletionChanged); HealthProcessor.Failed += onFail; foreach (var mod in Mods.Value.OfType()) @@ -374,7 +374,7 @@ namespace osu.Game.Screens.Play }, skipOutroOverlay = new SkipOverlay(Beatmap.Value.Storyboard.LatestEventTime ?? 0) { - RequestSkip = () => updateCompletionState(true), + RequestSkip = () => progressToResults(false), Alpha = 0 }, FailOverlay = new FailOverlay @@ -643,9 +643,8 @@ namespace osu.Game.Screens.Play /// /// Handles changes in player state which may progress the completion of gameplay / this screen's lifetime. /// - /// If in a state where a storyboard outro is to be played, offers the choice of skipping beyond it. /// Thrown if this method is called more than once without changing state. - private void updateCompletionState(bool skipStoryboardOutro = false) + private void scoreCompletionChanged(ValueChangedEvent completed) { // If this player instance is in the middle of an exit, don't attempt any kind of state update. if (!this.IsCurrentScreen()) @@ -656,7 +655,7 @@ namespace osu.Game.Screens.Play // Currently, even if this scenario is hit, prepareScoreForDisplay has already been queued (and potentially run). // In scenarios where rewinding is possible (replay, spectating) this is a non-issue as no submission/import work is done, // but it still doesn't feel right that this exists here. - if (!ScoreProcessor.HasCompleted.Value) + if (!completed.NewValue) { resultsDisplayDelegate?.Cancel(); resultsDisplayDelegate = null; @@ -667,7 +666,7 @@ namespace osu.Game.Screens.Play } if (resultsDisplayDelegate != null) - throw new InvalidOperationException(@$"{nameof(updateCompletionState)} should never be fired more than once."); + throw new InvalidOperationException(@$"{nameof(scoreCompletionChanged)} should never be fired more than once."); // Only show the completion screen if the player hasn't failed if (HealthProcessor.HasFailed) @@ -686,22 +685,49 @@ namespace osu.Game.Screens.Play // Asynchronously run score preparation operations (database import, online submission etc.). prepareScoreForDisplayTask ??= Task.Run(prepareScoreForResults); - if (skipStoryboardOutro) - { - scheduleCompletion(); - return; - } - bool storyboardHasOutro = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value; if (storyboardHasOutro) { + // if the current beatmap has a storyboard, the progression to results will be handled by the storyboard ending + // or the user pressing the skip outro button. skipOutroOverlay.Show(); return; } - using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY)) - scheduleCompletion(); + progressToResults(true); + } + + /// + /// Queue the results screen for display. + /// + /// + /// A final display will only occur once all work is completed in . + /// + /// Whether a minimum delay () should be added before the screen is displayed. + private void progressToResults(bool withDelay) + { + if (resultsDisplayDelegate != null) + return; + + double delay = withDelay ? RESULTS_DISPLAY_DELAY : 0; + + resultsDisplayDelegate = new ScheduledDelegate(() => + { + if (prepareScoreForDisplayTask?.IsCompleted != true) + // if the asynchronous preparation has not completed, keep repeating this delegate. + return; + + resultsDisplayDelegate?.Cancel(); + + if (!this.IsCurrentScreen()) + // This player instance may already be in the process of exiting. + return; + + this.Push(CreateResults(prepareScoreForDisplayTask.Result)); + }, Time.Current + delay, 50); + + Scheduler.Add(resultsDisplayDelegate); } private async Task prepareScoreForResults() @@ -734,7 +760,18 @@ namespace osu.Game.Screens.Play { if (!prepareScoreForDisplayTask.IsCompleted) { - scheduleCompletion(); + resultsDisplayDelegate = Schedule(() => + { + if (!prepareScoreForDisplayTask.IsCompleted) + { + scheduleCompletion(); + return; + } + + // screen may be in the exiting transition phase. + if (this.IsCurrentScreen()) + this.Push(CreateResults(prepareScoreForDisplayTask.Result)); + }); return; } From 752d0a9f0be0d5fa18580adf1bcfbb505e587d23 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 18 Jun 2021 16:08:14 +0900 Subject: [PATCH 436/520] add sound to scroll-to-top button --- osu.Game/Graphics/Containers/OsuHoverContainer.cs | 4 +++- osu.Game/Graphics/UserInterface/HoverSampleSet.cs | 5 ++++- osu.Game/Overlays/OverlayScrollContainer.cs | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index 67af79c763..ac66fd658a 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Input.Events; using osuTK.Graphics; using System.Collections.Generic; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.Containers { @@ -20,7 +21,8 @@ namespace osu.Game.Graphics.Containers protected virtual IEnumerable EffectTargets => new[] { Content }; - public OsuHoverContainer() + public OsuHoverContainer(HoverSampleSet sampleSet = HoverSampleSet.Default) + : base(sampleSet) { Enabled.ValueChanged += e => { diff --git a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs index c74ac90a4c..d74d13af95 100644 --- a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs +++ b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs @@ -20,6 +20,9 @@ namespace osu.Game.Graphics.UserInterface Toolbar, [Description("songselect")] - SongSelect + SongSelect, + + [Description("scrolltotop")] + ScrollToTop } } diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index 0004719b87..c5b4cc3645 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; @@ -84,6 +85,7 @@ namespace osu.Game.Overlays private readonly Box background; public ScrollToTopButton() + : base(HoverSampleSet.ScrollToTop) { Size = new Vector2(50); Alpha = 0; From 7ef8eac7737c3fb82364fef8575eb173bff34f6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 15:56:58 +0900 Subject: [PATCH 437/520] Remove unnecessary (and no longer correct) exception --- osu.Game/Screens/Play/Player.cs | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c8ba554c5f..5913e46836 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -665,9 +665,6 @@ namespace osu.Game.Screens.Play return; } - if (resultsDisplayDelegate != null) - throw new InvalidOperationException(@$"{nameof(scoreCompletionChanged)} should never be fired more than once."); - // Only show the completion screen if the player hasn't failed if (HealthProcessor.HasFailed) return; @@ -703,6 +700,7 @@ namespace osu.Game.Screens.Play ///
/// /// A final display will only occur once all work is completed in . + /// This means that even after calling this method, the results screen will never be shown until ScoreProcessor.HasCompleted becomes . /// /// Whether a minimum delay () should be added before the screen is displayed. private void progressToResults(bool withDelay) @@ -756,30 +754,6 @@ namespace osu.Game.Screens.Play return Score.ScoreInfo; } - private void scheduleCompletion() => resultsDisplayDelegate = Schedule(() => - { - if (!prepareScoreForDisplayTask.IsCompleted) - { - resultsDisplayDelegate = Schedule(() => - { - if (!prepareScoreForDisplayTask.IsCompleted) - { - scheduleCompletion(); - return; - } - - // screen may be in the exiting transition phase. - if (this.IsCurrentScreen()) - this.Push(CreateResults(prepareScoreForDisplayTask.Result)); - }); - return; - } - - // screen may be in the exiting transition phase. - if (this.IsCurrentScreen()) - this.Push(CreateResults(prepareScoreForDisplayTask.Result)); - }); - protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; #region Fail Logic From 06d1bd971cb5fdc37788efd49f8b8375074bde0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 16:08:49 +0900 Subject: [PATCH 438/520] Default `DrawableStoryboard` to a completed state to avoid state change on empty storyboards --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index ca041da801..8a31e4576a 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -25,7 +25,7 @@ namespace osu.Game.Storyboards.Drawables ///
public IBindable HasStoryboardEnded => hasStoryboardEnded; - private readonly BindableBool hasStoryboardEnded = new BindableBool(); + private readonly BindableBool hasStoryboardEnded = new BindableBool(true); protected override Container Content { get; } From 3819a1f03b510759032f5dc5e967964dcd0bab52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 16:12:34 +0900 Subject: [PATCH 439/520] Remove exit override behaviour I don't actually know under what scenario this could have been hit, and actually caused expected behaviour. Consider that in the scenario I describe in the comment (which I added yesterday), the user is requesting a pause or exit which would be "cancelled showing the results instead". But in such a scenario, `PerformExit` would first be run, which cancels the `resultsDisplayDelegate` in the first place. The only special case would be pressing the close button on the window decoration? Which I don't think should be a special case in the first place, so I'm just going to remove this for the time being to keep things simple. --- osu.Game/Screens/Play/Player.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5913e46836..5db9b47586 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -950,14 +950,6 @@ namespace osu.Game.Screens.Play { screenSuspension?.Expire(); - // if the results screen is prepared to be displayed, forcefully show it on an exit request. - // usually if a user has completed a play session they do want to see results. and if they don't they can hit the same key a second time. - if (resultsDisplayDelegate != null && !resultsDisplayDelegate.Cancelled && !resultsDisplayDelegate.Completed) - { - resultsDisplayDelegate.RunTask(); - return true; - } - // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. // To resolve test failures, forcefully end playing synchronously when this screen exits. // Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method. From f3426e38b422590a0f1f97ac096742254391bd9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 16:18:20 +0900 Subject: [PATCH 440/520] Add note about delay parameter --- osu.Game/Screens/Play/Player.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5db9b47586..f2d31e5dd2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -699,13 +699,19 @@ namespace osu.Game.Screens.Play /// Queue the results screen for display. ///
/// - /// A final display will only occur once all work is completed in . - /// This means that even after calling this method, the results screen will never be shown until ScoreProcessor.HasCompleted becomes . + /// A final display will only occur once all work is completed in . This means that even after calling this method, the results screen will never be shown until ScoreProcessor.HasCompleted becomes . + /// + /// Calling this method multiple times will have no effect. /// /// Whether a minimum delay () should be added before the screen is displayed. private void progressToResults(bool withDelay) { if (resultsDisplayDelegate != null) + // Note that if progressToResults is called one withDelay=true and then withDelay=false, this no-delay timing will not be + // accounted for. shouldn't be a huge concern (a user pressing the skip button after a results progression has already been queued + // may take x00 more milliseconds than expected in the very rare edge case). + // + // If required we can handle this more correctly by rescheduling here. return; double delay = withDelay ? RESULTS_DISPLAY_DELAY : 0; @@ -713,7 +719,7 @@ namespace osu.Game.Screens.Play resultsDisplayDelegate = new ScheduledDelegate(() => { if (prepareScoreForDisplayTask?.IsCompleted != true) - // if the asynchronous preparation has not completed, keep repeating this delegate. + // If the asynchronous preparation has not completed, keep repeating this delegate. return; resultsDisplayDelegate?.Cancel(); From 45122594e50ce23734e16be5332ee5b6603044cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 16:24:07 +0900 Subject: [PATCH 441/520] Remove the synchronous version of `PrepareScoreForResults` Replaces all existing usages with the `async` version. --- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 5 +++-- osu.Game/Screens/Play/Player.cs | 20 +++++-------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index cc1fb0b321..567ea6b988 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Screens; @@ -54,9 +55,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists return new PlaylistsResultsScreen(score, RoomId.Value.Value, PlaylistItem, true); } - protected override void PrepareScoreForResults(Score score) + protected override async Task PrepareScoreForResultsAsync(Score score) { - base.PrepareScoreForResults(score); + await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore()); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9fe12c58de..70cac9d27a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -626,7 +626,7 @@ namespace osu.Game.Screens.Play /// /// This delegate, when set, means the results screen has been queued to appear. - /// The display of the results screen may be delayed by any work being done in and . + /// The display of the results screen may be delayed by any work being done in . /// /// /// Once set, this can *only* be cancelled by rewinding, ie. if ScoreProcessor.HasCompleted becomes . @@ -706,9 +706,6 @@ namespace osu.Game.Screens.Play private async Task prepareScoreForResults() { - // ReSharper disable once MethodHasAsyncOverload - PrepareScoreForResults(Score); - try { await PrepareScoreForResultsAsync(Score).ConfigureAwait(false); @@ -1007,22 +1004,15 @@ namespace osu.Game.Screens.Play /// /// Prepare the for display at results. /// - /// - /// This is run synchronously before is run. - /// /// The to prepare. - protected virtual void PrepareScoreForResults(Score score) + /// A task that prepares the provided score. On completion, the score is assumed to be ready for display. + protected virtual Task PrepareScoreForResultsAsync(Score score) { // perform one final population to ensure everything is up-to-date. ScoreProcessor.PopulateScore(score.ScoreInfo); - } - /// - /// Prepare the for display at results. - /// - /// The to prepare. - /// A task that prepares the provided score. On completion, the score is assumed to be ready for display. - protected virtual Task PrepareScoreForResultsAsync(Score score) => Task.CompletedTask; + return Task.CompletedTask; + } /// /// Creates the for a . From 19507e107e7185609db5b71c817e842b0cd92909 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 16:46:40 +0900 Subject: [PATCH 442/520] Reorder methods to make more sense --- osu.Game/Screens/Play/Player.cs | 46 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 76f5e2336c..eab3770e27 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -695,6 +695,29 @@ namespace osu.Game.Screens.Play progressToResults(true); } + private async Task prepareScoreForResults() + { + try + { + await PrepareScoreForResultsAsync(Score).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.Error(ex, @"Score preparation failed!"); + } + + try + { + await ImportScore(Score).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.Error(ex, @"Score import failed!"); + } + + return Score.ScoreInfo; + } + /// /// Queue the results screen for display. /// @@ -734,29 +757,6 @@ namespace osu.Game.Screens.Play Scheduler.Add(resultsDisplayDelegate); } - private async Task prepareScoreForResults() - { - try - { - await PrepareScoreForResultsAsync(Score).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.Error(ex, @"Score preparation failed!"); - } - - try - { - await ImportScore(Score).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.Error(ex, @"Score import failed!"); - } - - return Score.ScoreInfo; - } - protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; #region Fail Logic From 0bc68a70181d1b09b5edbe59014d2a948a89c78b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 16:49:43 +0900 Subject: [PATCH 443/520] Move xmldoc to method --- osu.Game/Screens/Play/Player.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index eab3770e27..cadcc474b2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -679,7 +679,6 @@ namespace osu.Game.Screens.Play if (!Configuration.ShowResults) return; - // Asynchronously run score preparation operations (database import, online submission etc.). prepareScoreForDisplayTask ??= Task.Run(prepareScoreForResults); bool storyboardHasOutro = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value; @@ -695,6 +694,10 @@ namespace osu.Game.Screens.Play progressToResults(true); } + /// + /// Asynchronously run score preparation operations (database import, online submission etc.). + /// + /// The final score. private async Task prepareScoreForResults() { try From d06e52505a9b8105a8683ea31506a96f69528d26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 17:01:51 +0900 Subject: [PATCH 444/520] Fix thread safety of `KeyBindingStore.GetReadableKeyCombinationsFor` --- osu.Game/Input/RealmKeyBindingStore.cs | 21 ++++++++++++++------- osu.Game/OsuGame.cs | 4 ++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index e2efd546e7..45b7cf355f 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -26,16 +26,23 @@ namespace osu.Game.Input /// /// The action to lookup. /// A set of display strings for all the user's key configuration for the action. - public IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction) + public IReadOnlyList GetReadableKeyCombinationsFor(GlobalAction globalAction) { - foreach (var action in realmFactory.Context.All().Where(b => (GlobalAction)b.ActionInt == globalAction)) - { - string str = action.KeyCombination.ReadableString(); + List combinations = new List(); - // even if found, the readable string may be empty for an unbound action. - if (str.Length > 0) - yield return str; + using (var context = realmFactory.GetForRead()) + { + foreach (var action in context.Realm.All().Where(b => (GlobalAction)b.ActionInt == globalAction)) + { + string str = action.KeyCombination.ReadableString(); + + // even if found, the readable string may be empty for an unbound action. + if (str.Length > 0) + combinations.Add(str); + } } + + return combinations; } /// diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0c4d035728..907794d3bb 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -608,9 +608,9 @@ namespace osu.Game LocalConfig.LookupKeyBindings = l => { - var combinations = KeyBindingStore.GetReadableKeyCombinationsFor(l).ToArray(); + var combinations = KeyBindingStore.GetReadableKeyCombinationsFor(l); - if (combinations.Length == 0) + if (combinations.Count == 0) return "none"; return string.Join(" or ", combinations); From d5a1524eb0e0d1d530f7cacbc1557faad1358b34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 17:12:01 +0900 Subject: [PATCH 445/520] Add missing `rulesetID` check for global action matching --- osu.Game/Input/RealmKeyBindingStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 45b7cf355f..9089169877 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -32,7 +32,7 @@ namespace osu.Game.Input using (var context = realmFactory.GetForRead()) { - foreach (var action in context.Realm.All().Where(b => (GlobalAction)b.ActionInt == globalAction)) + foreach (var action in context.Realm.All().Where(b => b.RulesetID == null && (GlobalAction)b.ActionInt == globalAction)) { string str = action.KeyCombination.ReadableString(); From 5c59195e35b422b7e2481e529b9f43c4ce6bc153 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 17:34:45 +0900 Subject: [PATCH 446/520] Remove beta package allowance --- Directory.Build.props | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index a91d423043..53ad973e47 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -33,16 +33,12 @@ DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway. This is required due to https://github.com/NuGet/Home/issues/5740 - NU5104: - This is triggered on osu.Game due to using a beta/prerelease version of realm. - Warning suppression can be removed after migrating off of a beta release. - CA9998: Microsoft.CodeAnalysis.FxCopAnalyzers has been deprecated. The entire package will be able to be removed after migrating to .NET 5, as analysers are shipped as part of the .NET 5 SDK anyway. --> - $(NoWarn);NU1701;NU5104;CA9998 + $(NoWarn);NU1701;CA9998 false From 36b5414b1d83693cbc5049a6a67d3ded89f87295 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 17:45:30 +0900 Subject: [PATCH 447/520] Update comment to hopefully explain a weird conditional better --- osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 5f500d3023..10376c1866 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -83,8 +83,9 @@ namespace osu.Game.Input.Bindings var defaults = DefaultKeyBindings.ToList(); if (ruleset != null && !ruleset.ID.HasValue) - // if the provided ruleset is not stored to the database, we have no way to retrieve custom bindings. - // fallback to defaults instead. + // some tests instantiate a ruleset which is not present in the database. + // in these cases we still want key bindings to work, but matching to database instances would result in none being present, + // so let's populate the defaults directly. KeyBindings = defaults; else { From 4feb7c848f1e13e6030045c1fb06507fd0a58714 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 18 Jun 2021 17:26:28 +0900 Subject: [PATCH 448/520] add sound to tab controls --- osu.Game/Graphics/UserInterface/HoverSampleSet.cs | 3 +++ osu.Game/Graphics/UserInterface/OsuTabControl.cs | 2 +- osu.Game/Graphics/UserInterface/PageTabControl.cs | 2 +- osu.Game/Overlays/OverlayTabControl.cs | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs index d74d13af95..646131ed4f 100644 --- a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs +++ b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs @@ -22,6 +22,9 @@ namespace osu.Game.Graphics.UserInterface [Description("songselect")] SongSelect, + [Description("tabselect")] + TabSelect, + [Description("scrolltotop")] ScrollToTop } diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 0c220336a5..c447d7f609 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -172,7 +172,7 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, }, - new HoverClickSounds() + new HoverClickSounds(HoverSampleSet.TabSelect) }; } diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs index 1ba9ad53bb..a218c7bf52 100644 --- a/osu.Game/Graphics/UserInterface/PageTabControl.cs +++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs @@ -76,7 +76,7 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, }, - new HoverClickSounds() + new HoverClickSounds(HoverSampleSet.TabSelect) }; Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Torus, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true); diff --git a/osu.Game/Overlays/OverlayTabControl.cs b/osu.Game/Overlays/OverlayTabControl.cs index a1cbf2c1e7..578cd703c7 100644 --- a/osu.Game/Overlays/OverlayTabControl.cs +++ b/osu.Game/Overlays/OverlayTabControl.cs @@ -99,7 +99,7 @@ namespace osu.Game.Overlays ExpandedSize = 5f, CollapsedSize = 0 }, - new HoverClickSounds() + new HoverClickSounds(HoverSampleSet.TabSelect) }; } From d462394635c67dbf24238fdccfb38916474f134a Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 18 Jun 2021 18:57:40 +0900 Subject: [PATCH 449/520] add sound to dropdowns --- .../Graphics/UserInterface/HoverSampleSet.cs | 3 -- .../Graphics/UserInterface/OsuDropdown.cs | 29 ++++++++++++++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs index 646131ed4f..3a8187c8f4 100644 --- a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs +++ b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs @@ -13,9 +13,6 @@ namespace osu.Game.Graphics.UserInterface [Description("button")] Button, - [Description("softer")] - Soft, - [Description("toolbar")] Toolbar, diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 15fb00ccb0..24d062e059 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -4,6 +4,8 @@ using System.Linq; using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -57,6 +59,9 @@ namespace osu.Game.Graphics.UserInterface { public override bool HandleNonPositionalInput => State == MenuState.Open; + private Sample sampleOpen; + private Sample sampleClose; + // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring public OsuDropdownMenu() { @@ -69,9 +74,25 @@ namespace osu.Game.Graphics.UserInterface ItemsContainer.Padding = new MarginPadding(5); } + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleOpen = audio.Samples.Get(@"UI/dropdown-open"); + sampleClose = audio.Samples.Get(@"UI/dropdown-close"); + } + // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring - protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint); - protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint); + protected override void AnimateOpen() + { + this.FadeIn(300, Easing.OutQuint); + sampleOpen?.Play(); + } + + protected override void AnimateClose() + { + this.FadeOut(300, Easing.OutQuint); + sampleClose?.Play(); + } // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring protected override void UpdateSize(Vector2 newSize) @@ -155,7 +176,7 @@ namespace osu.Game.Graphics.UserInterface nonAccentSelectedColour = Color4.Black.Opacity(0.5f); updateColours(); - AddInternal(new HoverClickSounds(HoverSampleSet.Soft)); + AddInternal(new HoverSounds()); } protected override void UpdateForegroundColour() @@ -262,7 +283,7 @@ namespace osu.Game.Graphics.UserInterface }, }; - AddInternal(new HoverClickSounds()); + AddInternal(new HoverSounds()); } [BackgroundDependencyLoader] From 36d2199a0241173a8931fab9f17ef56a2191970e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Jun 2021 19:20:57 +0900 Subject: [PATCH 450/520] Add exception on Apply() while loading --- .../Pooling/PoolableDrawableWithLifetime.cs | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 3ab85aa214..faa82786cd 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -28,12 +28,9 @@ namespace osu.Game.Rulesets.Objects.Pooling public TEntry? Entry { get => entry; - set { - if (LoadState < LoadState.Ready) - entry = value; - else if (value != null) + if (value != null) Apply(value); else if (HasEntryApplied) free(); @@ -86,7 +83,7 @@ namespace osu.Game.Rulesets.Objects.Pooling // Apply the initial entry. if (Entry != null && !HasEntryApplied) - Apply(Entry); + apply(Entry); } /// @@ -95,16 +92,10 @@ namespace osu.Game.Rulesets.Objects.Pooling /// public void Apply(TEntry entry) { - if (HasEntryApplied) - free(); + if (LoadState == LoadState.Loading) + throw new InvalidOperationException($"Cannot apply a new {nameof(TEntry)} while currently loading."); - this.entry = entry; - entry.LifetimeChanged += setLifetimeFromEntry; - setLifetimeFromEntry(entry); - - OnApply(entry); - - HasEntryApplied = true; + apply(entry); } protected sealed override void FreeAfterUse() @@ -130,6 +121,20 @@ namespace osu.Game.Rulesets.Objects.Pooling { } + private void apply(TEntry entry) + { + if (HasEntryApplied) + free(); + + this.entry = entry; + entry.LifetimeChanged += setLifetimeFromEntry; + setLifetimeFromEntry(entry); + + OnApply(entry); + + HasEntryApplied = true; + } + private void free() { Debug.Assert(Entry != null && HasEntryApplied); From 36d51d5117e8a149de7d057fcf9e116d395133bf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Jun 2021 19:23:37 +0900 Subject: [PATCH 451/520] Don't set entry immediately --- .../Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index faa82786cd..3b261494ff 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -30,7 +30,9 @@ namespace osu.Game.Rulesets.Objects.Pooling get => entry; set { - if (value != null) + if (LoadState == LoadState.NotLoaded) + entry = value; + else if (value != null) Apply(value); else if (HasEntryApplied) free(); From 42c5a962fbbc9a0ff871c387c6d8345104b45276 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Jun 2021 19:27:10 +0900 Subject: [PATCH 452/520] Add xmldoc remark --- .../Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 3b261494ff..9c6097a048 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Objects.Pooling /// /// /// If a non-null value is set before loading is started, the entry is applied when the loading is completed. + /// It is not valid to set an entry while this is loading. /// public TEntry? Entry { From 78c5ccda605a26883268359a792c0cd5013db750 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 19:18:57 +0900 Subject: [PATCH 453/520] Fix renaming a ruleset DLL causing a startup crash --- osu.Game/Rulesets/RulesetStore.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 0a34ca9598..06bb14ce17 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using Microsoft.EntityFrameworkCore.Internal; using osu.Framework; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; @@ -96,13 +97,25 @@ namespace osu.Game.Rulesets context.SaveChanges(); - // add any other modes var existingRulesets = context.RulesetInfo.ToList(); + // add any other rulesets which have assemblies present but are not yet in the database. foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) { if (existingRulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) - context.RulesetInfo.Add(r.RulesetInfo); + { + var existingSameShortName = existingRulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); + + if (existingSameShortName != null) + { + // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. + // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. + // in such cases, update the instantiation info of the existing entry to point to the new one. + existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; + } + else + context.RulesetInfo.Add(r.RulesetInfo); + } } context.SaveChanges(); From 2dadc9d686071ae4e1df0e5c6859f584c28174ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 19:39:00 +0900 Subject: [PATCH 454/520] Remove unused using statement --- osu.Game/Rulesets/RulesetStore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 06bb14ce17..1f12f3dfeb 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using Microsoft.EntityFrameworkCore.Internal; using osu.Framework; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; From 953683044f4440051bd39f9ff294f172aa095dd0 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 18 Jun 2021 21:00:08 +0900 Subject: [PATCH 455/520] fix checkbox sounds not being used for certain checkboxes --- .../UserInterface/OsuTabControlCheckbox.cs | 22 ++++++++++++++++--- osu.Game/Overlays/Comments/CommentsHeader.cs | 17 ++++++++++++++ osu.Game/Overlays/Comments/HeaderButton.cs | 2 -- osu.Game/Overlays/OverlaySortTabControl.cs | 2 ++ 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index b66a4a58ce..c6121dcd17 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -4,6 +4,8 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -43,6 +45,8 @@ namespace osu.Game.Graphics.UserInterface } private const float transition_length = 500; + private Sample sampleChecked; + private Sample sampleUnchecked; public OsuTabControlCheckbox() { @@ -77,8 +81,7 @@ namespace osu.Game.Graphics.UserInterface Colour = Color4.White, Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, - }, - new HoverClickSounds() + } }; Current.ValueChanged += selected => @@ -91,10 +94,13 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, AudioManager audio) { if (accentColour == null) AccentColour = colours.Blue; + + sampleChecked = audio.Samples.Get(@"UI/check-on"); + sampleUnchecked = audio.Samples.Get(@"UI/check-off"); } protected override bool OnHover(HoverEvent e) @@ -111,6 +117,16 @@ namespace osu.Game.Graphics.UserInterface base.OnHoverLost(e); } + protected override void OnUserChange(bool value) + { + base.OnUserChange(value); + + if (value) + sampleChecked?.Play(); + else + sampleUnchecked?.Play(); + } + private void updateFade() { box.FadeTo(Current.Value || IsHovered ? 1 : 0, transition_length, Easing.OutQuint); diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs index 0dd68bbd41..bf80655c3d 100644 --- a/osu.Game/Overlays/Comments/CommentsHeader.cs +++ b/osu.Game/Overlays/Comments/CommentsHeader.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Bindables; @@ -66,6 +68,8 @@ namespace osu.Game.Overlays.Comments public readonly BindableBool Checked = new BindableBool(); private readonly SpriteIcon checkboxIcon; + private Sample sampleChecked; + private Sample sampleUnchecked; public ShowDeletedButton() { @@ -93,6 +97,13 @@ namespace osu.Game.Overlays.Comments }); } + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleChecked = audio.Samples.Get(@"UI/check-on"); + sampleUnchecked = audio.Samples.Get(@"UI/check-off"); + } + protected override void LoadComplete() { Checked.BindValueChanged(isChecked => checkboxIcon.Icon = isChecked.NewValue ? FontAwesome.Solid.CheckSquare : FontAwesome.Regular.Square, true); @@ -102,6 +113,12 @@ namespace osu.Game.Overlays.Comments protected override bool OnClick(ClickEvent e) { Checked.Value = !Checked.Value; + + if (Checked.Value) + sampleChecked?.Play(); + else + sampleUnchecked?.Play(); + return true; } } diff --git a/osu.Game/Overlays/Comments/HeaderButton.cs b/osu.Game/Overlays/Comments/HeaderButton.cs index fdc8db35ab..65172aa57c 100644 --- a/osu.Game/Overlays/Comments/HeaderButton.cs +++ b/osu.Game/Overlays/Comments/HeaderButton.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Comments { @@ -39,7 +38,6 @@ namespace osu.Game.Overlays.Comments Origin = Anchor.Centre, Margin = new MarginPadding { Horizontal = 10 } }, - new HoverClickSounds(), }); } diff --git a/osu.Game/Overlays/OverlaySortTabControl.cs b/osu.Game/Overlays/OverlaySortTabControl.cs index b230acca11..d4dde0db3f 100644 --- a/osu.Game/Overlays/OverlaySortTabControl.cs +++ b/osu.Game/Overlays/OverlaySortTabControl.cs @@ -148,6 +148,8 @@ namespace osu.Game.Overlays } } }); + + AddInternal(new HoverClickSounds()); } protected override void LoadComplete() From 6e4fc26e168b2d18b6ffbcd94e00d27011134c47 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 18 Jun 2021 21:02:17 +0900 Subject: [PATCH 456/520] replace 'songselect' hover/click sounds with 'button' ones for now --- osu.Game/Graphics/UserInterface/HoverSampleSet.cs | 3 --- osu.Game/Screens/Select/FooterButton.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs index 3a8187c8f4..b88f81a143 100644 --- a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs +++ b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs @@ -16,9 +16,6 @@ namespace osu.Game.Graphics.UserInterface [Description("toolbar")] Toolbar, - [Description("songselect")] - SongSelect, - [Description("tabselect")] TabSelect, diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index afb3943a09..c3fbd767ff 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Select private readonly Box light; public FooterButton() - : base(HoverSampleSet.SongSelect) + : base(HoverSampleSet.Button) { AutoSizeAxes = Axes.Both; Shear = SHEAR; From 5ce52b2669201b85c788d325f007681d41dd14d8 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 18 Jun 2021 21:14:51 +0900 Subject: [PATCH 457/520] fix ModButton duplicate click sound --- osu.Game/Overlays/Mods/ModButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 5e3733cd5e..70424101fd 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -302,7 +302,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.TopCentre, Font = OsuFont.GetFont(size: 18) }, - new HoverClickSounds(buttons: new[] { MouseButton.Left, MouseButton.Right }) + new HoverSounds() }; Mod = mod; From 390abccb4bf56503bda7b29934e649d0a60ee7a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 22:08:59 +0900 Subject: [PATCH 458/520] Add workaround for dropdowns playing close animation on first display --- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 24d062e059..b97f12df02 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -81,9 +81,13 @@ namespace osu.Game.Graphics.UserInterface sampleClose = audio.Samples.Get(@"UI/dropdown-close"); } + // todo: this shouldn't be required after https://github.com/ppy/osu-framework/issues/4519 is fixed. + private bool wasOpened; + // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring protected override void AnimateOpen() { + wasOpened = true; this.FadeIn(300, Easing.OutQuint); sampleOpen?.Play(); } @@ -91,7 +95,8 @@ namespace osu.Game.Graphics.UserInterface protected override void AnimateClose() { this.FadeOut(300, Easing.OutQuint); - sampleClose?.Play(); + if (wasOpened) + sampleClose?.Play(); } // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring From 1f6b4b10ab93b5175ad1a0e222ca9a17a80ade30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 22:16:15 +0900 Subject: [PATCH 459/520] Update resources --- 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 1f60f02fb1..3a1e6ba9a3 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 68ffb87c6c..fe7214de38 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 8aa79762fc..01c9b27cc7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 1ec03bf6fac74b678a543849c90062808c73b181 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 22:25:24 +0900 Subject: [PATCH 460/520] Update resources --- 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 490e43b5e6..54469eec43 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8eeaad1127..560e5e2493 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index db442238ce..7b3033db9c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 69c1cd5b3430ef536c5c220bd77ed7398c4bab3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Jun 2021 14:17:37 +0200 Subject: [PATCH 461/520] Add failing test case for hit circle animations disable --- .../Editor/TestSceneOsuEditorHitAnimations.cs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorHitAnimations.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorHitAnimations.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorHitAnimations.cs new file mode 100644 index 0000000000..94ee5d7e14 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorHitAnimations.cs @@ -0,0 +1,71 @@ +// 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.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Configuration; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + [TestFixture] + public class TestSceneOsuEditorHitAnimations : TestSceneOsuEditor + { + [Resolved] + private OsuConfigManager config { get; set; } + + [Test] + public void TestHitCircleAnimationDisable() + { + HitCircle hitCircle = null; + + AddStep("retrieve first hit circle", () => hitCircle = getHitCircle(0)); + toggleAnimations(true); + seekSmoothlyTo(() => hitCircle.StartTime + 10); + + AddAssert("hit circle piece has transforms", () => + { + var drawableHitCircle = (DrawableHitCircle)getDrawableObjectFor(hitCircle); + return getTransformsRecursively(drawableHitCircle.CirclePiece).Any(t => t.EndTime > EditorClock.CurrentTime); + }); + + AddStep("retrieve second hit circle", () => hitCircle = getHitCircle(1)); + toggleAnimations(false); + seekSmoothlyTo(() => hitCircle.StartTime + 10); + + AddAssert("hit circle piece has no transforms", () => + { + var drawableHitCircle = (DrawableHitCircle)getDrawableObjectFor(hitCircle); + return getTransformsRecursively(drawableHitCircle.CirclePiece).All(t => t.EndTime <= EditorClock.CurrentTime); + }); + } + + private HitCircle getHitCircle(int index) + => EditorBeatmap.HitObjects.OfType().ElementAt(index); + + private DrawableHitObject getDrawableObjectFor(HitObject hitObject) + => this.ChildrenOfType().Single(ho => ho.HitObject == hitObject); + + private IEnumerable getTransformsRecursively(Drawable drawable) + => drawable.ChildrenOfType().SelectMany(d => d.Transforms); + + private void toggleAnimations(bool enabled) + => AddStep($"toggle animations {(enabled ? "on" : "off")}", () => config.SetValue(OsuSetting.EditorHitAnimations, enabled)); + + private void seekSmoothlyTo(Func targetTime) + { + AddStep("seek smoothly", () => EditorClock.SeekSmoothlyTo(targetTime.Invoke())); + AddUntilStep("wait for seek", () => Precision.AlmostEquals(targetTime.Invoke(), EditorClock.CurrentTime)); + } + } +} From e2a370f60244a0ccac1450ed604a79a2be468a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Jun 2021 14:57:04 +0200 Subject: [PATCH 462/520] Add coverage for hit circle fade-out duration --- .../Editor/TestSceneOsuEditorHitAnimations.cs | 19 +++++++++++-------- .../Edit/DrawableOsuEditorRuleset.cs | 16 ++++++++-------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorHitAnimations.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorHitAnimations.cs index 94ee5d7e14..18de3a8414 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorHitAnimations.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorHitAnimations.cs @@ -13,6 +13,7 @@ using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -28,25 +29,27 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public void TestHitCircleAnimationDisable() { HitCircle hitCircle = null; + DrawableHitCircle drawableHitCircle = null; AddStep("retrieve first hit circle", () => hitCircle = getHitCircle(0)); toggleAnimations(true); seekSmoothlyTo(() => hitCircle.StartTime + 10); - AddAssert("hit circle piece has transforms", () => - { - var drawableHitCircle = (DrawableHitCircle)getDrawableObjectFor(hitCircle); - return getTransformsRecursively(drawableHitCircle.CirclePiece).Any(t => t.EndTime > EditorClock.CurrentTime); - }); + AddStep("retrieve drawable", () => drawableHitCircle = (DrawableHitCircle)getDrawableObjectFor(hitCircle)); + AddAssert("hit circle piece has transforms", + () => getTransformsRecursively(drawableHitCircle.CirclePiece).Any(t => t.EndTime > EditorClock.CurrentTime)); AddStep("retrieve second hit circle", () => hitCircle = getHitCircle(1)); toggleAnimations(false); seekSmoothlyTo(() => hitCircle.StartTime + 10); - AddAssert("hit circle piece has no transforms", () => + AddStep("retrieve drawable", () => drawableHitCircle = (DrawableHitCircle)getDrawableObjectFor(hitCircle)); + AddAssert("hit circle piece has no transforms", + () => getTransformsRecursively(drawableHitCircle.CirclePiece).All(t => t.EndTime <= EditorClock.CurrentTime)); + AddAssert("hit circle has longer fade-out applied", () => { - var drawableHitCircle = (DrawableHitCircle)getDrawableObjectFor(hitCircle); - return getTransformsRecursively(drawableHitCircle.CirclePiece).All(t => t.EndTime <= EditorClock.CurrentTime); + var alphaTransform = drawableHitCircle.Transforms.Last(t => t.TargetMember == nameof(Alpha)); + return alphaTransform.EndTime - alphaTransform.StartTime == DrawableOsuEditorRuleset.EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION; }); } diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs index aeeae84d14..9143b154b9 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs @@ -20,6 +20,12 @@ namespace osu.Game.Rulesets.Osu.Edit { public class DrawableOsuEditorRuleset : DrawableOsuRuleset { + /// + /// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay. + /// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points. + /// + public const double EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION = 700; + public DrawableOsuEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { @@ -46,12 +52,6 @@ namespace osu.Game.Rulesets.Osu.Edit d.ApplyCustomUpdateState += updateState; } - /// - /// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay. - /// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points. - /// - private const double editor_hit_object_fade_out_extension = 700; - private void updateState(DrawableHitObject hitObject, ArmedState state) { if (state == ArmedState.Idle || hitAnimations.Value) @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (hitObject is DrawableHitCircle circle) { circle.ApproachCircle - .FadeOutFromOne(editor_hit_object_fade_out_extension * 4) + .FadeOutFromOne(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION * 4) .Expire(); circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint); @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Edit hitObject.RemoveTransform(existing); using (hitObject.BeginAbsoluteSequence(hitObject.HitStateUpdateTime)) - hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); + hitObject.FadeOut(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION).Expire(); break; } } From fe48ddfee3242cde208b64cb7a59967f0d670885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Jun 2021 15:26:31 +0200 Subject: [PATCH 463/520] Also cover slider animation disable --- .../Editor/TestSceneOsuEditorHitAnimations.cs | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorHitAnimations.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorHitAnimations.cs index 18de3a8414..7ffa2c1f94 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorHitAnimations.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorHitAnimations.cs @@ -36,16 +36,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor seekSmoothlyTo(() => hitCircle.StartTime + 10); AddStep("retrieve drawable", () => drawableHitCircle = (DrawableHitCircle)getDrawableObjectFor(hitCircle)); - AddAssert("hit circle piece has transforms", - () => getTransformsRecursively(drawableHitCircle.CirclePiece).Any(t => t.EndTime > EditorClock.CurrentTime)); + assertFutureTransforms(() => drawableHitCircle.CirclePiece, true); AddStep("retrieve second hit circle", () => hitCircle = getHitCircle(1)); toggleAnimations(false); seekSmoothlyTo(() => hitCircle.StartTime + 10); AddStep("retrieve drawable", () => drawableHitCircle = (DrawableHitCircle)getDrawableObjectFor(hitCircle)); - AddAssert("hit circle piece has no transforms", - () => getTransformsRecursively(drawableHitCircle.CirclePiece).All(t => t.EndTime <= EditorClock.CurrentTime)); + assertFutureTransforms(() => drawableHitCircle.CirclePiece, false); AddAssert("hit circle has longer fade-out applied", () => { var alphaTransform = drawableHitCircle.Transforms.Last(t => t.TargetMember == nameof(Alpha)); @@ -53,9 +51,47 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); } + [Test] + public void TestSliderAnimationDisable() + { + Slider slider = null; + DrawableSlider drawableSlider = null; + DrawableSliderRepeat sliderRepeat = null; + + AddStep("retrieve first slider with repeats", () => slider = getSliderWithRepeats(0)); + toggleAnimations(true); + seekSmoothlyTo(() => slider.StartTime + slider.SpanDuration + 10); + + retrieveDrawables(); + assertFutureTransforms(() => sliderRepeat, true); + + AddStep("retrieve second slider with repeats", () => slider = getSliderWithRepeats(1)); + toggleAnimations(false); + seekSmoothlyTo(() => slider.StartTime + slider.SpanDuration + 10); + + retrieveDrawables(); + assertFutureTransforms(() => sliderRepeat.Arrow, false); + seekSmoothlyTo(() => slider.GetEndTime()); + AddAssert("slider has longer fade-out applied", () => + { + var alphaTransform = drawableSlider.Transforms.Last(t => t.TargetMember == nameof(Alpha)); + return alphaTransform.EndTime - alphaTransform.StartTime == DrawableOsuEditorRuleset.EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION; + }); + + void retrieveDrawables() => + AddStep("retrieve drawables", () => + { + drawableSlider = (DrawableSlider)getDrawableObjectFor(slider); + sliderRepeat = (DrawableSliderRepeat)getDrawableObjectFor(slider.NestedHitObjects.OfType().First()); + }); + } + private HitCircle getHitCircle(int index) => EditorBeatmap.HitObjects.OfType().ElementAt(index); + private Slider getSliderWithRepeats(int index) + => EditorBeatmap.HitObjects.OfType().Where(s => s.RepeatCount >= 1).ElementAt(index); + private DrawableHitObject getDrawableObjectFor(HitObject hitObject) => this.ChildrenOfType().Single(ho => ho.HitObject == hitObject); @@ -70,5 +106,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("seek smoothly", () => EditorClock.SeekSmoothlyTo(targetTime.Invoke())); AddUntilStep("wait for seek", () => Precision.AlmostEquals(targetTime.Invoke(), EditorClock.CurrentTime)); } + + private void assertFutureTransforms(Func getDrawable, bool hasFutureTransforms) + => AddAssert($"object {(hasFutureTransforms ? "has" : "has no")} future transforms", + () => getTransformsRecursively(getDrawable()).Any(t => t.EndTime >= EditorClock.CurrentTime) == hasFutureTransforms); } } From e94fbd83e2690c5c34c8ccd0cebdacaae636f4e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Jun 2021 15:30:45 +0200 Subject: [PATCH 464/520] Ensure editor ruleset animation disable execution order --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs index 9143b154b9..54fd95a618 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs @@ -69,8 +69,14 @@ namespace osu.Game.Rulesets.Osu.Edit if (hitObject is IHasMainCirclePiece mainPieceContainer) { // clear any explode animation logic. - mainPieceContainer.CirclePiece.ApplyTransformsAt(hitObject.HitStateUpdateTime, true); - mainPieceContainer.CirclePiece.ClearTransformsAfter(hitObject.HitStateUpdateTime, true); + // this is scheduled after children to ensure that the clear happens after invocations of ApplyCustomUpdateState on the circle piece's nested skinnables. + ScheduleAfterChildren(() => + { + if (hitObject.HitObject == null) return; + + mainPieceContainer.CirclePiece.ApplyTransformsAt(hitObject.HitStateUpdateTime, true); + mainPieceContainer.CirclePiece.ClearTransformsAfter(hitObject.HitStateUpdateTime, true); + }); } if (hitObject is DrawableSliderRepeat repeat) From afc89b39d9cb751ab00555c50a055cb607774603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Jun 2021 15:32:51 +0200 Subject: [PATCH 465/520] Use `StateUpdateTime` for transform clearing logic `MainCirclePiece` specifies a state transform starting at `StateUpdateTime`, which is earlier than the previously-used `HitStateUpdateTime`. Change the transform clearing logic to use the former to ensure that exactly all animation transforms are cleared. --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs index 54fd95a618..0e61c02e2d 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs @@ -74,15 +74,15 @@ namespace osu.Game.Rulesets.Osu.Edit { if (hitObject.HitObject == null) return; - mainPieceContainer.CirclePiece.ApplyTransformsAt(hitObject.HitStateUpdateTime, true); - mainPieceContainer.CirclePiece.ClearTransformsAfter(hitObject.HitStateUpdateTime, true); + mainPieceContainer.CirclePiece.ApplyTransformsAt(hitObject.StateUpdateTime, true); + mainPieceContainer.CirclePiece.ClearTransformsAfter(hitObject.StateUpdateTime, true); }); } if (hitObject is DrawableSliderRepeat repeat) { - repeat.Arrow.ApplyTransformsAt(hitObject.HitStateUpdateTime, true); - repeat.Arrow.ClearTransformsAfter(hitObject.HitStateUpdateTime, true); + repeat.Arrow.ApplyTransformsAt(hitObject.StateUpdateTime, true); + repeat.Arrow.ClearTransformsAfter(hitObject.StateUpdateTime, true); } // adjust the visuals of top-level object types to make them stay on screen for longer than usual. From 843c8bd7a44e7b244214818d6e6765e997fc1bf5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 18 Jun 2021 20:33:50 +0300 Subject: [PATCH 466/520] Move spinner approach circle to its own `SkinnableDrawable` --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +- .../Objects/Drawables/DrawableSpinner.cs | 12 ++-- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 2 +- .../Skinning/Legacy/LegacySpinner.cs | 31 --------- .../Legacy/LegacySpinnerApproachCircle.cs | 64 +++++++++++++++++++ .../Legacy/OsuLegacySkinTransformer.cs | 6 ++ 6 files changed, 76 insertions(+), 41 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index c635aab5f5..d712ffd92a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Mods // todo: hide background using (spinner.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt)) - spinner.HideApproachCircle(); + spinner.ApproachCircle.Hide(); using (spinner.BeginAbsoluteSequence(fadeStartTime)) spinner.FadeOut(fadeDuration); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 942cc52c50..4507b1520c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -29,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public new OsuSpinnerJudgementResult Result => (OsuSpinnerJudgementResult)base.Result; + public SkinnableDrawable ApproachCircle { get; private set; } + public SpinnerRotationTracker RotationTracker { get; private set; } private SpinnerSpmCalculator spmCalculator; @@ -42,8 +44,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; - internal readonly Bindable ApproachCircleVisibility = new Bindable(Visibility.Visible); - /// /// The amount of bonus score gained from spinning after the required number of spins, for display purposes. /// @@ -88,7 +88,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Y, Children = new Drawable[] { - new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinner()), + ApproachCircle = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerApproachCircle)), + new SkinnableSpinnerBody(ApproachCircle.CreateProxy(), _ => new DefaultSpinner()), RotationTracker = new SpinnerRotationTracker(this) } }, @@ -287,11 +288,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables updateBonusScore(); } - /// - /// Hides the spinner's approach circle if it has one. - /// - public void HideApproachCircle() => this.TransformBindableTo(ApproachCircleVisibility, Visibility.Hidden); - private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult; private int wholeSpins; diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index fcb544fa5b..687fc1f966 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -10,7 +10,6 @@ namespace osu.Game.Rulesets.Osu Cursor, CursorTrail, SliderScorePoint, - ApproachCircle, ReverseArrow, HitCircleText, SliderHeadHitCircle, @@ -19,5 +18,6 @@ namespace osu.Game.Rulesets.Osu SliderBall, SliderBody, SpinnerBody, + SpinnerApproachCircle, } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 5b0c2d405b..f32caab8ac 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -32,7 +32,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected DrawableSpinner DrawableSpinner { get; private set; } - private Drawable approachCircle; private Sprite spin; private Sprite clear; @@ -61,7 +60,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy RelativeSizeAxes = Axes.Both, Children = new[] { - approachCircle = getSpinnerApproachCircle(source), spin = new Sprite { Anchor = Anchor.TopCentre, @@ -104,28 +102,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy }.With(s => s.Font = s.Font.With(fixedWidth: false)), } }); - - static Drawable getSpinnerApproachCircle(ISkinSource source) - { - var spinnerProvider = source.FindProvider(s => - s.GetTexture("spinner-circle") != null || - s.GetTexture("spinner-top") != null); - - if (spinnerProvider is DefaultLegacySkin) - return Empty(); - - return new Sprite - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-approachcircle"), - Scale = new Vector2(SPRITE_SCALE * 1.86f), - Y = SPINNER_Y_CENTRE, - }; - } } - private IBindable approachCircleVisibility; private IBindable gainedBonus; private IBindable spinsPerMinute; @@ -135,12 +113,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { base.LoadComplete(); - approachCircleVisibility = DrawableSpinner.ApproachCircleVisibility.GetBoundCopy(); - approachCircleVisibility.BindValueChanged(v => - { - approachCircle.Alpha = v.NewValue == Visibility.Hidden ? 0 : 1; - }, true); - gainedBonus = DrawableSpinner.GainedBonus.GetBoundCopy(); gainedBonus.BindValueChanged(bonus => { @@ -204,9 +176,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy spmCounter.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out); } - using (BeginAbsoluteSequence(d.HitObject.StartTime)) - approachCircle.ScaleTo(SPRITE_SCALE * 1.86f).ScaleTo(SPRITE_SCALE * 0.1f, d.HitObject.Duration); - double spinFadeOutLength = Math.Min(400, d.HitObject.Duration); using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength, true)) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs new file mode 100644 index 0000000000..92454cefa3 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs @@ -0,0 +1,64 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Skinning; +using osuTK; +using static osu.Game.Rulesets.Osu.Skinning.Legacy.LegacySpinner; + +namespace osu.Game.Rulesets.Osu.Skinning.Legacy +{ + public class LegacySpinnerApproachCircle : CompositeDrawable + { + private DrawableSpinner drawableSpinner; + + [CanBeNull] + private Sprite sprite; + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableHitObject, ISkinSource source) + { + drawableSpinner = (DrawableSpinner)drawableHitObject; + + AutoSizeAxes = Axes.Both; + + var spinnerProvider = source.FindProvider(s => + s.GetTexture("spinner-circle") != null || + s.GetTexture("spinner-top") != null); + + if (spinnerProvider is DefaultLegacySkin) + return; + + InternalChild = sprite = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-approachcircle"), + Scale = new Vector2(SPRITE_SCALE * 1.86f), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); + } + + private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) + { + if (!(drawableHitObject is DrawableSpinner spinner)) + return; + + using (BeginAbsoluteSequence(spinner.HitObject.StartTime)) + sprite?.ScaleTo(SPRITE_SCALE * 0.1f, spinner.HitObject.Duration); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 3267b48ebf..a8c42a3773 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -121,6 +121,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return new LegacyOldStyleSpinner(); return null; + + case OsuSkinComponents.SpinnerApproachCircle: + if (Source.GetTexture("spinner-approachcircle") != null) + return new LegacySpinnerApproachCircle(); + + return null; } } From d6b9436151a9b0a7f658f4f4c07f5230329de115 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 18 Jun 2021 20:34:25 +0300 Subject: [PATCH 467/520] Proxy spinner approach circle before the spinner overlay components --- .../Skinning/IProxiesApproachCircle.cs | 12 +++++++ .../Skinning/Legacy/LegacySpinner.cs | 13 ++++--- .../Skinning/SkinnableSpinnerBody.cs | 34 +++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/IProxiesApproachCircle.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/SkinnableSpinnerBody.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/IProxiesApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/IProxiesApproachCircle.cs new file mode 100644 index 0000000000..3cc0026adc --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/IProxiesApproachCircle.cs @@ -0,0 +1,12 @@ +// 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.Graphics.Containers; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public interface IProxiesApproachCircle + { + Container ApproachCircleTarget { get; } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index f32caab8ac..efbb27bf3f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -15,8 +15,10 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public abstract class LegacySpinner : CompositeDrawable + public abstract class LegacySpinner : CompositeDrawable, IProxiesApproachCircle { + public const float SPRITE_SCALE = 0.625f; + /// /// All constants are in osu!stable's gamefield space, which is shifted 16px downwards. /// This offset is negated in both osu!stable and osu!lazer to bring all constants into window-space. @@ -26,12 +28,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected const float SPINNER_Y_CENTRE = SPINNER_TOP_OFFSET + 219f; - protected const float SPRITE_SCALE = 0.625f; - private const float spm_hide_offset = 50f; protected DrawableSpinner DrawableSpinner { get; private set; } + public Container ApproachCircleTarget { get; private set; } private Sprite spin; private Sprite clear; @@ -58,8 +59,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Depth = float.MinValue, RelativeSizeAxes = Axes.Both, - Children = new[] + Children = new Drawable[] { + ApproachCircleTarget = new Container + { + RelativeSizeAxes = Axes.Both, + }, spin = new Sprite { Anchor = Anchor.TopCentre, diff --git a/osu.Game.Rulesets.Osu/Skinning/SkinnableSpinnerBody.cs b/osu.Game.Rulesets.Osu/Skinning/SkinnableSpinnerBody.cs new file mode 100644 index 0000000000..763b9dd677 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/SkinnableSpinnerBody.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 System; +using osu.Framework.Graphics; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + /// + /// A skinnable drawable of the component, with the approach circle exposed for modification. + /// + public class SkinnableSpinnerBody : SkinnableDrawable + { + private readonly Drawable approachCircleProxy; + + public SkinnableSpinnerBody(Drawable approachCircleProxy, Func defaultImplementation = null) + : base(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), defaultImplementation) + { + this.approachCircleProxy = approachCircleProxy; + } + + protected override void SkinChanged(ISkinSource skin) + { + if (Drawable is IProxiesApproachCircle oldProxiesApproachCircle) + oldProxiesApproachCircle.ApproachCircleTarget.Remove(approachCircleProxy); + + base.SkinChanged(skin); + + if (Drawable is IProxiesApproachCircle newProxiesApproachCircle) + newProxiesApproachCircle.ApproachCircleTarget.Add(approachCircleProxy); + } + } +} From 76db87f9cb273ca92e7cf61fc7faef898d74712a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 19 Jun 2021 08:00:36 +0200 Subject: [PATCH 468/520] Try-catch around localisation store registration Some platforms (android, older windows versions) will throw exceptions at runtime when an unsupported `CultureInfo` is attempted to be instantiated, leading to nasty crashes. Add a preventative try-catch registration to prevent the crash, and log the errors for visibility. --- osu.Game/OsuGame.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0c4d035728..7455df361c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -55,6 +55,7 @@ using osu.Game.IO; using osu.Game.Localisation; using osu.Game.Performance; using osu.Game.Skinning.Editor; +using osu.Framework.Extensions; namespace osu.Game { @@ -585,7 +586,15 @@ namespace osu.Game foreach (var language in Enum.GetValues(typeof(Language)).OfType()) { var cultureCode = language.ToCultureCode(); - Localisation.AddLanguage(cultureCode, new ResourceManagerLocalisationStore(cultureCode)); + + try + { + Localisation.AddLanguage(cultureCode, new ResourceManagerLocalisationStore(cultureCode)); + } + catch (Exception ex) + { + Logger.Error(ex, $"Could not load localisations for language \"{cultureCode}\""); + } } // The next time this is updated is in UpdateAfterChildren, which occurs too late and results From b47774b55a4d146e4d5a24a8daeafbde2f551cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 19 Jun 2021 08:07:37 +0200 Subject: [PATCH 469/520] Remove Tagalog language for now Rationale given in inline comment. --- osu.Game/Localisation/Language.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index 65541feedf..3c66f31c58 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -86,8 +86,10 @@ namespace osu.Game.Localisation [Description(@"ไทย")] th, - [Description(@"Tagalog")] - tl, + // Tagalog has no associated localisations yet, and is not supported on Xamarin platforms or Windows versions <10. + // Can be revisited if localisations ever arrive. + //[Description(@"Tagalog")] + //tl, [Description(@"Türkçe")] tr, From afcc3e14f436d0e413bf5e6dfa0cdca844f6956d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 19 Jun 2021 16:16:29 +0900 Subject: [PATCH 470/520] m --- osu.Game/OsuGame.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7455df361c..d7cdb8b396 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -55,7 +55,6 @@ using osu.Game.IO; using osu.Game.Localisation; using osu.Game.Performance; using osu.Game.Skinning.Editor; -using osu.Framework.Extensions; namespace osu.Game { From 805e6cca75ef9d6637c10497959aadabdb163a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 19 Jun 2021 11:07:13 +0200 Subject: [PATCH 471/520] Add direct references to Realm from Xamarin projects Fixes crashes on launch due to missing `realm-wrapper` transitive dependency. --- osu.Android.props | 4 ++++ osu.iOS.props | 1 + 2 files changed, 5 insertions(+) diff --git a/osu.Android.props b/osu.Android.props index 3a1e6ba9a3..1dc99bb60a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,4 +54,8 @@ + + + + diff --git a/osu.iOS.props b/osu.iOS.props index 01c9b27cc7..3689ce51f2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -99,5 +99,6 @@ + From fa87aa6be51ec0558ea0a94522751c52dd3330c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 19 Jun 2021 11:10:37 +0200 Subject: [PATCH 472/520] Add autogenerated FodyWeavers files to Xamarin projects --- osu.Android/FodyWeavers.xml | 3 +++ osu.Android/FodyWeavers.xsd | 34 ++++++++++++++++++++++++++++++++++ osu.iOS/FodyWeavers.xml | 3 +++ osu.iOS/FodyWeavers.xsd | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+) create mode 100644 osu.Android/FodyWeavers.xml create mode 100644 osu.Android/FodyWeavers.xsd create mode 100644 osu.iOS/FodyWeavers.xml create mode 100644 osu.iOS/FodyWeavers.xsd diff --git a/osu.Android/FodyWeavers.xml b/osu.Android/FodyWeavers.xml new file mode 100644 index 0000000000..cc07b89533 --- /dev/null +++ b/osu.Android/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/osu.Android/FodyWeavers.xsd b/osu.Android/FodyWeavers.xsd new file mode 100644 index 0000000000..447878c551 --- /dev/null +++ b/osu.Android/FodyWeavers.xsd @@ -0,0 +1,34 @@ + + + + + + + + + + + Disables anonymized usage information from being sent on build. Read more about what data is being collected and why here: https://github.com/realm/realm-dotnet/blob/master/Realm/Realm.Fody/Common/Analytics.cs + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/osu.iOS/FodyWeavers.xml b/osu.iOS/FodyWeavers.xml new file mode 100644 index 0000000000..cc07b89533 --- /dev/null +++ b/osu.iOS/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/osu.iOS/FodyWeavers.xsd b/osu.iOS/FodyWeavers.xsd new file mode 100644 index 0000000000..447878c551 --- /dev/null +++ b/osu.iOS/FodyWeavers.xsd @@ -0,0 +1,34 @@ + + + + + + + + + + + Disables anonymized usage information from being sent on build. Read more about what data is being collected and why here: https://github.com/realm/realm-dotnet/blob/master/Realm/Realm.Fody/Common/Analytics.cs + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file From c04b09520da8115080bec4dfc4310e9f549493e3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 19 Jun 2021 20:06:28 +0300 Subject: [PATCH 473/520] Replace spinner approach circle proxying logic with hooking up to `OnSkinChange` in mod --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 15 ++++++-- .../Objects/Drawables/DrawableSpinner.cs | 5 ++- .../Skinning/IHasSpinnerApproachCircle.cs | 18 ++++++++++ .../Skinning/IProxiesApproachCircle.cs | 12 ------- .../Skinning/Legacy/LegacySpinner.cs | 13 ++++--- .../Legacy/LegacySpinnerApproachCircle.cs | 9 +++-- .../Skinning/SkinnableSpinnerBody.cs | 34 ------------------- 7 files changed, 45 insertions(+), 61 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/IHasSpinnerApproachCircle.cs delete mode 100644 osu.Game.Rulesets.Osu/Skinning/IProxiesApproachCircle.cs delete mode 100644 osu.Game.Rulesets.Osu/Skinning/SkinnableSpinnerBody.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index d712ffd92a..071c3dc3f1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Skinning; namespace osu.Game.Rulesets.Osu.Mods { @@ -110,8 +111,8 @@ namespace osu.Game.Rulesets.Osu.Mods // hide elements we don't care about. // todo: hide background - using (spinner.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt)) - spinner.ApproachCircle.Hide(); + spinner.Body.OnSkinChanged += () => hideSpinnerApproachCircle(spinner); + hideSpinnerApproachCircle(spinner); using (spinner.BeginAbsoluteSequence(fadeStartTime)) spinner.FadeOut(fadeDuration); @@ -163,5 +164,15 @@ namespace osu.Game.Rulesets.Osu.Mods } } } + + private static void hideSpinnerApproachCircle(DrawableSpinner spinner) + { + var approachCircle = (spinner.Body.Drawable as IHasSpinnerApproachCircle)?.ApproachCircle; + if (approachCircle == null) + return; + + using (spinner.BeginAbsoluteSequence(spinner.HitObject.StartTime - spinner.HitObject.TimePreempt)) + approachCircle.Hide(); + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 4507b1520c..ec87d3bfdf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public new OsuSpinnerJudgementResult Result => (OsuSpinnerJudgementResult)base.Result; - public SkinnableDrawable ApproachCircle { get; private set; } + public SkinnableDrawable Body { get; private set; } public SpinnerRotationTracker RotationTracker { get; private set; } @@ -88,8 +88,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Y, Children = new Drawable[] { - ApproachCircle = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerApproachCircle)), - new SkinnableSpinnerBody(ApproachCircle.CreateProxy(), _ => new DefaultSpinner()), + Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinner()), RotationTracker = new SpinnerRotationTracker(this) } }, diff --git a/osu.Game.Rulesets.Osu/Skinning/IHasSpinnerApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/IHasSpinnerApproachCircle.cs new file mode 100644 index 0000000000..dcfc15913c --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/IHasSpinnerApproachCircle.cs @@ -0,0 +1,18 @@ +// 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.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + /// + /// A common interface between implementations of the component that provide approach circles for the spinner. + /// + public interface IHasSpinnerApproachCircle + { + /// + /// The spinner approach circle. + /// + Drawable ApproachCircle { get; } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/IProxiesApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/IProxiesApproachCircle.cs deleted file mode 100644 index 3cc0026adc..0000000000 --- a/osu.Game.Rulesets.Osu/Skinning/IProxiesApproachCircle.cs +++ /dev/null @@ -1,12 +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 osu.Framework.Graphics.Containers; - -namespace osu.Game.Rulesets.Osu.Skinning -{ - public interface IProxiesApproachCircle - { - Container ApproachCircleTarget { get; } - } -} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index efbb27bf3f..5471de22d4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public abstract class LegacySpinner : CompositeDrawable, IProxiesApproachCircle + public abstract class LegacySpinner : CompositeDrawable, IHasSpinnerApproachCircle { public const float SPRITE_SCALE = 0.625f; @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected DrawableSpinner DrawableSpinner { get; private set; } - public Container ApproachCircleTarget { get; private set; } + public Drawable ApproachCircle { get; private set; } private Sprite spin; private Sprite clear; @@ -59,11 +59,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Depth = float.MinValue, RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Children = new[] { - ApproachCircleTarget = new Container + ApproachCircle = new LegacySpinnerApproachCircle { - RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Scale = new Vector2(SPRITE_SCALE), + Y = SPINNER_Y_CENTRE, }, spin = new Sprite { diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs index 92454cefa3..d5e510cd69 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK; -using static osu.Game.Rulesets.Osu.Skinning.Legacy.LegacySpinner; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private DrawableSpinner drawableSpinner; [CanBeNull] - private Sprite sprite; + private Sprite approachCircle; [BackgroundDependencyLoader] private void load(DrawableHitObject drawableHitObject, ISkinSource source) @@ -35,12 +34,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (spinnerProvider is DefaultLegacySkin) return; - InternalChild = sprite = new Sprite + InternalChild = approachCircle = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-approachcircle"), - Scale = new Vector2(SPRITE_SCALE * 1.86f), + Scale = new Vector2(1.86f), }; } @@ -58,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return; using (BeginAbsoluteSequence(spinner.HitObject.StartTime)) - sprite?.ScaleTo(SPRITE_SCALE * 0.1f, spinner.HitObject.Duration); + approachCircle?.ScaleTo(0.1f, spinner.HitObject.Duration); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/SkinnableSpinnerBody.cs b/osu.Game.Rulesets.Osu/Skinning/SkinnableSpinnerBody.cs deleted file mode 100644 index 763b9dd677..0000000000 --- a/osu.Game.Rulesets.Osu/Skinning/SkinnableSpinnerBody.cs +++ /dev/null @@ -1,34 +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.Graphics; -using osu.Game.Skinning; - -namespace osu.Game.Rulesets.Osu.Skinning -{ - /// - /// A skinnable drawable of the component, with the approach circle exposed for modification. - /// - public class SkinnableSpinnerBody : SkinnableDrawable - { - private readonly Drawable approachCircleProxy; - - public SkinnableSpinnerBody(Drawable approachCircleProxy, Func defaultImplementation = null) - : base(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), defaultImplementation) - { - this.approachCircleProxy = approachCircleProxy; - } - - protected override void SkinChanged(ISkinSource skin) - { - if (Drawable is IProxiesApproachCircle oldProxiesApproachCircle) - oldProxiesApproachCircle.ApproachCircleTarget.Remove(approachCircleProxy); - - base.SkinChanged(skin); - - if (Drawable is IProxiesApproachCircle newProxiesApproachCircle) - newProxiesApproachCircle.ApproachCircleTarget.Add(approachCircleProxy); - } - } -} From c3217fd8b1f4d9169f416577b3ca07e4ada8a76c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 19 Jun 2021 20:10:32 +0300 Subject: [PATCH 474/520] Remove leftover approach circle skin component --- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 1 - .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 6 ------ 2 files changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 687fc1f966..46e501758b 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -18,6 +18,5 @@ namespace osu.Game.Rulesets.Osu SliderBall, SliderBody, SpinnerBody, - SpinnerApproachCircle, } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index a8c42a3773..3267b48ebf 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -121,12 +121,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return new LegacyOldStyleSpinner(); return null; - - case OsuSkinComponents.SpinnerApproachCircle: - if (Source.GetTexture("spinner-approachcircle") != null) - return new LegacySpinnerApproachCircle(); - - return null; } } From 46b9fd9ac8a3778201586016d577db9d775524c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Jun 2021 09:36:18 +0200 Subject: [PATCH 475/520] Remove and ignore FodyWeavers schema files --- .gitignore | 3 +++ osu.Android/FodyWeavers.xsd | 34 ---------------------------------- osu.Game/FodyWeavers.xsd | 34 ---------------------------------- osu.iOS/FodyWeavers.xsd | 34 ---------------------------------- 4 files changed, 3 insertions(+), 102 deletions(-) delete mode 100644 osu.Android/FodyWeavers.xsd delete mode 100644 osu.Game/FodyWeavers.xsd delete mode 100644 osu.iOS/FodyWeavers.xsd diff --git a/.gitignore b/.gitignore index d122d25054..de6a3ac848 100644 --- a/.gitignore +++ b/.gitignore @@ -336,3 +336,6 @@ inspectcode /BenchmarkDotNet.Artifacts *.GeneratedMSBuildEditorConfig.editorconfig + +# Fody (pulled in by Realm) - schema file +FodyWeavers.xsd diff --git a/osu.Android/FodyWeavers.xsd b/osu.Android/FodyWeavers.xsd deleted file mode 100644 index 447878c551..0000000000 --- a/osu.Android/FodyWeavers.xsd +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - Disables anonymized usage information from being sent on build. Read more about what data is being collected and why here: https://github.com/realm/realm-dotnet/blob/master/Realm/Realm.Fody/Common/Analytics.cs - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/osu.Game/FodyWeavers.xsd b/osu.Game/FodyWeavers.xsd deleted file mode 100644 index 447878c551..0000000000 --- a/osu.Game/FodyWeavers.xsd +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - Disables anonymized usage information from being sent on build. Read more about what data is being collected and why here: https://github.com/realm/realm-dotnet/blob/master/Realm/Realm.Fody/Common/Analytics.cs - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/osu.iOS/FodyWeavers.xsd b/osu.iOS/FodyWeavers.xsd deleted file mode 100644 index 447878c551..0000000000 --- a/osu.iOS/FodyWeavers.xsd +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - Disables anonymized usage information from being sent on build. Read more about what data is being collected and why here: https://github.com/realm/realm-dotnet/blob/master/Realm/Realm.Fody/Common/Analytics.cs - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file From 1f383532f2a7bee5caa6bc496e887c8dce0979dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Jun 2021 09:37:23 +0200 Subject: [PATCH 476/520] Move FodyWeavers configuration to solution root --- osu.Android/FodyWeavers.xml => FodyWeavers.xml | 0 osu.Game/FodyWeavers.xml | 3 --- osu.iOS/FodyWeavers.xml | 3 --- 3 files changed, 6 deletions(-) rename osu.Android/FodyWeavers.xml => FodyWeavers.xml (100%) delete mode 100644 osu.Game/FodyWeavers.xml delete mode 100644 osu.iOS/FodyWeavers.xml diff --git a/osu.Android/FodyWeavers.xml b/FodyWeavers.xml similarity index 100% rename from osu.Android/FodyWeavers.xml rename to FodyWeavers.xml diff --git a/osu.Game/FodyWeavers.xml b/osu.Game/FodyWeavers.xml deleted file mode 100644 index cc07b89533..0000000000 --- a/osu.Game/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/osu.iOS/FodyWeavers.xml b/osu.iOS/FodyWeavers.xml deleted file mode 100644 index cc07b89533..0000000000 --- a/osu.iOS/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file From 32bd3107e1929cd8b9f1bb5f636ced0b8175da1f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sun, 20 Jun 2021 17:07:41 +0900 Subject: [PATCH 477/520] Remove high performance GC setting --- osu.Game/Performance/HighPerformanceSession.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Performance/HighPerformanceSession.cs b/osu.Game/Performance/HighPerformanceSession.cs index 96e67669c5..661c1046f1 100644 --- a/osu.Game/Performance/HighPerformanceSession.cs +++ b/osu.Game/Performance/HighPerformanceSession.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 System.Runtime; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -11,7 +10,6 @@ namespace osu.Game.Performance public class HighPerformanceSession : Component { private readonly IBindable localUserPlaying = new Bindable(); - private GCLatencyMode originalGCMode; [BackgroundDependencyLoader] private void load(OsuGame game) @@ -34,14 +32,10 @@ namespace osu.Game.Performance protected virtual void EnableHighPerformanceSession() { - originalGCMode = GCSettings.LatencyMode; - GCSettings.LatencyMode = GCLatencyMode.LowLatency; } protected virtual void DisableHighPerformanceSession() { - if (GCSettings.LatencyMode == GCLatencyMode.LowLatency) - GCSettings.LatencyMode = originalGCMode; } } } From 72155a7c525818431fef2c76565eb3995a1f769a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 03:37:49 +0300 Subject: [PATCH 478/520] Replace if pattern-matching check with switch cases instead --- .../Skinning/Legacy/LegacySpinnerApproachCircle.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs index d5e510cd69..d0ce8fbb47 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs @@ -53,11 +53,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { - if (!(drawableHitObject is DrawableSpinner spinner)) - return; + switch (drawableHitObject) + { + case DrawableSpinner spinner: + using (BeginAbsoluteSequence(spinner.HitObject.StartTime)) + approachCircle?.ScaleTo(0.1f, spinner.HitObject.Duration); - using (BeginAbsoluteSequence(spinner.HitObject.StartTime)) - approachCircle?.ScaleTo(0.1f, spinner.HitObject.Duration); + break; + } } } } From 01478d780da7c2ca1da8b1d74a9fc1503b3435d5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 03:43:11 +0300 Subject: [PATCH 479/520] Generalize `IHasSpinnerApproachCircle` from being spinner-specifc --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +- .../{IHasSpinnerApproachCircle.cs => IHasApproachCircle.cs} | 6 +++--- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game.Rulesets.Osu/Skinning/{IHasSpinnerApproachCircle.cs => IHasApproachCircle.cs} (59%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 071c3dc3f1..16b38cd0b1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Mods private static void hideSpinnerApproachCircle(DrawableSpinner spinner) { - var approachCircle = (spinner.Body.Drawable as IHasSpinnerApproachCircle)?.ApproachCircle; + var approachCircle = (spinner.Body.Drawable as IHasApproachCircle)?.ApproachCircle; if (approachCircle == null) return; diff --git a/osu.Game.Rulesets.Osu/Skinning/IHasSpinnerApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/IHasApproachCircle.cs similarity index 59% rename from osu.Game.Rulesets.Osu/Skinning/IHasSpinnerApproachCircle.cs rename to osu.Game.Rulesets.Osu/Skinning/IHasApproachCircle.cs index dcfc15913c..88aa715ad9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/IHasSpinnerApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/IHasApproachCircle.cs @@ -6,12 +6,12 @@ using osu.Framework.Graphics; namespace osu.Game.Rulesets.Osu.Skinning { /// - /// A common interface between implementations of the component that provide approach circles for the spinner. + /// A common interface between skin component implementations which provide an approach circle. /// - public interface IHasSpinnerApproachCircle + public interface IHasApproachCircle { /// - /// The spinner approach circle. + /// The approach circle drawable. /// Drawable ApproachCircle { get; } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 5471de22d4..37379f4646 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public abstract class LegacySpinner : CompositeDrawable, IHasSpinnerApproachCircle + public abstract class LegacySpinner : CompositeDrawable, IHasApproachCircle { public const float SPRITE_SCALE = 0.625f; From 5cfd0e32236a5a63eaf4132e6fe9dea22569bf2c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 04:16:58 +0300 Subject: [PATCH 480/520] Remove implicit `LegacySkin` check and refactor anything using it --- .../Skinning/RulesetSkinProvidingContainer.cs | 31 +++++++++++++------ osu.Game/Tests/Visual/TestPlayer.cs | 2 +- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index f11acd981a..bbaeee98d8 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Skinning Ruleset = ruleset; Beatmap = beatmap; - InternalChild = new BeatmapSkinProvidingContainer(GetRulesetTransformedSkin(beatmapSkin)) + InternalChild = new BeatmapSkinProvidingContainer(beatmapSkin is LegacySkin ? GetLegacyRulesetTransformedSkin(beatmapSkin) : beatmapSkin) { Child = Content = new Container { @@ -54,23 +54,34 @@ namespace osu.Game.Skinning { SkinSources.Clear(); - SkinSources.Add(GetRulesetTransformedSkin(skinManager.CurrentSkin.Value)); + // TODO: we also want to insert a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. - // TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. - if (skinManager.CurrentSkin.Value is LegacySkin && skinManager.CurrentSkin.Value != skinManager.DefaultLegacySkin) - SkinSources.Add(GetRulesetTransformedSkin(skinManager.DefaultLegacySkin)); + switch (skinManager.CurrentSkin.Value) + { + case LegacySkin currentLegacySkin: + SkinSources.Add(GetLegacyRulesetTransformedSkin(currentLegacySkin)); + + if (currentLegacySkin != skinManager.DefaultLegacySkin) + SkinSources.Add(GetLegacyRulesetTransformedSkin(skinManager.DefaultLegacySkin)); + + break; + + default: + SkinSources.Add(skinManager.CurrentSkin.Value); + break; + } } - protected ISkin GetRulesetTransformedSkin(ISkin skin) + protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) { - if (!(skin is LegacySkin)) - return skin; + if (legacySkin == null) + return null; - var rulesetTransformed = Ruleset.CreateLegacySkinProvider(skin, Beatmap); + var rulesetTransformed = Ruleset.CreateLegacySkinProvider(legacySkin, Beatmap); if (rulesetTransformed != null) return rulesetTransformed; - return skin; + return legacySkin; } } } diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index e1431b0658..2be5d8ac9f 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual base.UpdateSkins(); if (skin != null) - SkinSources.Insert(0, GetRulesetTransformedSkin(skin)); + SkinSources.Insert(0, skin is LegacySkin ? GetLegacyRulesetTransformedSkin(skin) : skin); } } } From e4705abee2bb7b2017838440cd187154e0feefbf Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 20 Jun 2021 20:07:46 -0700 Subject: [PATCH 481/520] Update custom rulesets directory link in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3054f19e79..2213b42121 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ If your platform is not listed above, there is still a chance you can manually b osu! is designed to have extensible modular gameplay modes, called "rulesets". Building one of these allows a developer to harness the power of osu! for their own game style. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates). -You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/issues/5852). +You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/discussions/13096). ## Developing osu! From 11b78ad849d333f8cd0ad79267e7ed4506603daf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 04:27:09 +0300 Subject: [PATCH 482/520] Make `TestPlayer` skin assigning logic not flaky --- .../Beatmaps/LegacyBeatmapSkinColourTest.cs | 2 +- osu.Game/Tests/Visual/PlayerTestScene.cs | 2 +- osu.Game/Tests/Visual/TestPlayer.cs | 20 ++++++++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs index 1feb3eebbf..347b611579 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps ExposedPlayer player = CreateTestPlayer(); - player.Skin = new TestSkin(userHasCustomColours); + player.SetSkin(new TestSkin(userHasCustomColours)); LoadScreen(player); diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index e42a043eec..f5fad895e2 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual } Player = CreatePlayer(ruleset); - Player.Skin = GetPlayerSkin(); + Player.SetSkin(GetPlayerSkin()); LoadScreen(Player); } diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 2be5d8ac9f..19fd7068b9 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -1,7 +1,9 @@ // 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 JetBrains.Annotations; using osu.Framework.Allocation; @@ -22,8 +24,6 @@ namespace osu.Game.Tests.Visual ///
public class TestPlayer : Player { - public ISkin Skin { get; set; } - protected override bool PauseOnFocusLost { get; } public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; @@ -81,8 +81,22 @@ namespace osu.Game.Tests.Visual ScoreProcessor.NewJudgement += r => Results.Add(r); } + public ISkin Skin { get; private set; } + + private TestSkinProvidingContainer rulesetSkinProvider; + + internal void SetSkin(ISkin skin) + { + Debug.Assert(rulesetSkinProvider == null); + + if (Skin != null) + throw new InvalidOperationException("A skin has already been set."); + + Skin = skin; + } + protected override RulesetSkinProvidingContainer CreateRulesetSkinProvider(Ruleset ruleset, IBeatmap beatmap, ISkin beatmapSkin) - => new TestSkinProvidingContainer(Skin, ruleset, beatmap, beatmapSkin); + => rulesetSkinProvider = new TestSkinProvidingContainer(Skin, ruleset, beatmap, beatmapSkin); private class TestSkinProvidingContainer : RulesetSkinProvidingContainer { From 68e28f4903937d9c2ddd821b1147df9ac9af192f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 07:35:07 +0300 Subject: [PATCH 483/520] Implement `IHasApproachCircle` in `DrawableHitCircle` as well --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs | 5 ++++- osu.Game.Rulesets.Osu/Skinning/IHasApproachCircle.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index ca2e6578db..46fc8f99b2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -12,6 +12,7 @@ using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; @@ -19,7 +20,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableHitCircle : DrawableOsuHitObject, IHasMainCirclePiece + public class DrawableHitCircle : DrawableOsuHitObject, IHasMainCirclePiece, IHasApproachCircle { public OsuAction? HitAction => HitArea.HitAction; protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle; @@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public HitReceptor HitArea { get; private set; } public SkinnableDrawable CirclePiece { get; private set; } + Drawable IHasApproachCircle.ApproachCircle => ApproachCircle; + private Container scaleContainer; private InputManager inputManager; diff --git a/osu.Game.Rulesets.Osu/Skinning/IHasApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/IHasApproachCircle.cs index 88aa715ad9..7fbc5b144b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/IHasApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/IHasApproachCircle.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; namespace osu.Game.Rulesets.Osu.Skinning { /// - /// A common interface between skin component implementations which provide an approach circle. + /// A common interface between implementations which provide an approach circle. /// public interface IHasApproachCircle { From 8b2110c048d28a79244581e0d10fbf33ba1a578f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 20 Jun 2021 21:36:08 -0700 Subject: [PATCH 484/520] Add failing discussion links test --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index ecb37706b0..2c2c4dc24e 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -28,6 +28,8 @@ namespace osu.Game.Tests.Chat [TestCase(LinkAction.OpenBeatmapSet, "123", "https://dev.ppy.sh/beatmapsets/123")] [TestCase(LinkAction.OpenBeatmapSet, "123", "https://dev.ppy.sh/beatmapsets/123/whatever")] [TestCase(LinkAction.External, "https://dev.ppy.sh/beatmapsets/abc", "https://dev.ppy.sh/beatmapsets/abc")] + [TestCase(LinkAction.External, "https://dev.ppy.sh/beatmapsets/discussions", "https://dev.ppy.sh/beatmapsets/discussions")] + [TestCase(LinkAction.External, "https://dev.ppy.sh/beatmapsets/discussions/123", "https://dev.ppy.sh/beatmapsets/discussions/123")] public void TestBeatmapLinks(LinkAction expectedAction, string expectedArg, string link) { MessageFormatter.WebsiteRootUrl = "dev.ppy.sh"; From 6fda5e569ad5b4489aacd23c529570f870efd90e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 20 Jun 2021 21:37:00 -0700 Subject: [PATCH 485/520] Fix beatmap discussion links wrongly leading to beatmap page --- osu.Game/Online/Chat/MessageFormatter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index df14d7eb1c..faee08742b 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -154,6 +154,10 @@ namespace osu.Game.Online.Chat case "beatmapsets": case "d": { + if (mainArg == "discussions") + // handle discussion links externally for now + return new LinkDetails(LinkAction.External, url); + if (args.Length > 4 && int.TryParse(args[4], out var id)) // https://osu.ppy.sh/beatmapsets/1154158#osu/2768184 return new LinkDetails(LinkAction.OpenBeatmap, id.ToString()); From 42edbe4fb91741f22a076e3f7cc474ab2ddb6ef3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 08:40:38 +0300 Subject: [PATCH 486/520] Move `ApproachCircle` implementation into per-style --- .../Skinning/Legacy/LegacyNewStyleSpinner.cs | 31 ++++++++++++++----- .../Skinning/Legacy/LegacyOldStyleSpinner.cs | 12 ++++++- .../Skinning/Legacy/LegacySpinner.cs | 14 +++------ 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index 22fb3aab86..09b0d83bb4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -31,12 +31,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private Container scaleContainer; + public override Drawable ApproachCircle { get; protected set; } + [BackgroundDependencyLoader] private void load(ISkinSource source) { AddInternal(scaleContainer = new Container { - Scale = new Vector2(SPRITE_SCALE), Anchor = Anchor.TopCentre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, @@ -48,6 +49,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Anchor = Anchor.Centre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-glow"), + Scale = new Vector2(SPRITE_SCALE), Blending = BlendingParameters.Additive, Colour = glowColour, }, @@ -55,28 +57,43 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-bottom") + Texture = source.GetTexture("spinner-bottom"), + Scale = new Vector2(SPRITE_SCALE), }, discTop = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-top") + Texture = source.GetTexture("spinner-top"), + Scale = new Vector2(SPRITE_SCALE), }, fixedMiddle = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-middle") + Texture = source.GetTexture("spinner-middle"), + Scale = new Vector2(SPRITE_SCALE), }, spinningMiddle = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-middle2") - } + Texture = source.GetTexture("spinner-middle2"), + Scale = new Vector2(SPRITE_SCALE), + }, } }); + + if (!(source.FindProvider(s => s.GetTexture("spinner-top") != null) is DefaultLegacySkin)) + { + scaleContainer.Add(ApproachCircle = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-approachcircle"), + Scale = new Vector2(SPRITE_SCALE * 1.86f), + }); + } } protected override void UpdateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) @@ -126,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy glow.Alpha = DrawableSpinner.Progress; - scaleContainer.Scale = new Vector2(SPRITE_SCALE * (0.8f + (float)Interpolation.ApplyEasing(Easing.Out, DrawableSpinner.Progress) * 0.2f)); + scaleContainer.Scale = new Vector2(0.8f + (float)Interpolation.ApplyEasing(Easing.Out, DrawableSpinner.Progress) * 0.2f); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs index d80e061662..8f162806de 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs @@ -29,12 +29,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private const float final_metre_height = 692 * SPRITE_SCALE; + public override Drawable ApproachCircle { get; protected set; } + [BackgroundDependencyLoader] private void load(ISkinSource source) { spinnerBlink = source.GetConfig(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true; - AddRangeInternal(new Drawable[] + AddRangeInternal(new[] { new Sprite { @@ -68,6 +70,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Origin = Anchor.TopLeft, Scale = new Vector2(SPRITE_SCALE) } + }, + ApproachCircle = new Sprite + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-approachcircle"), + Scale = new Vector2(SPRITE_SCALE * 1.86f), + Y = SPINNER_Y_CENTRE, } }); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 37379f4646..3bbb523cfe 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected DrawableSpinner DrawableSpinner { get; private set; } - public Drawable ApproachCircle { get; private set; } + public abstract Drawable ApproachCircle { get; protected set; } private Sprite spin; private Sprite clear; @@ -59,15 +59,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Depth = float.MinValue, RelativeSizeAxes = Axes.Both, - Children = new[] + Children = new Drawable[] { - ApproachCircle = new LegacySpinnerApproachCircle - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - Scale = new Vector2(SPRITE_SCALE), - Y = SPINNER_Y_CENTRE, - }, spin = new Sprite { Anchor = Anchor.TopCentre, @@ -184,6 +177,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy spmCounter.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out); } + using (BeginAbsoluteSequence(d.HitObject.StartTime)) + ApproachCircle?.ScaleTo(SPRITE_SCALE * 0.1f, d.HitObject.Duration); + double spinFadeOutLength = Math.Min(400, d.HitObject.Duration); using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength, true)) From 036b745425441007a7aa9c377aff4697622b0295 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 08:41:10 +0300 Subject: [PATCH 487/520] Remove no longer needed `LegacySpinnerApproachCircle` --- .../Legacy/LegacySpinnerApproachCircle.cs | 66 ------------------- 1 file changed, 66 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs deleted file mode 100644 index d0ce8fbb47..0000000000 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerApproachCircle.cs +++ /dev/null @@ -1,66 +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 JetBrains.Annotations; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Skinning; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Skinning.Legacy -{ - public class LegacySpinnerApproachCircle : CompositeDrawable - { - private DrawableSpinner drawableSpinner; - - [CanBeNull] - private Sprite approachCircle; - - [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableHitObject, ISkinSource source) - { - drawableSpinner = (DrawableSpinner)drawableHitObject; - - AutoSizeAxes = Axes.Both; - - var spinnerProvider = source.FindProvider(s => - s.GetTexture("spinner-circle") != null || - s.GetTexture("spinner-top") != null); - - if (spinnerProvider is DefaultLegacySkin) - return; - - InternalChild = approachCircle = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-approachcircle"), - Scale = new Vector2(1.86f), - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); - } - - private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) - { - switch (drawableHitObject) - { - case DrawableSpinner spinner: - using (BeginAbsoluteSequence(spinner.HitObject.StartTime)) - approachCircle?.ScaleTo(0.1f, spinner.HitObject.Duration); - - break; - } - } - } -} From ba15f7c19b1c13b59a8eec859a0312efec07aba9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 10:47:38 +0300 Subject: [PATCH 488/520] Move `ApproachCircle` out of the scale container and revert relevant changes --- .../Skinning/Legacy/LegacyNewStyleSpinner.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index 09b0d83bb4..82a2598443 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { AddInternal(scaleContainer = new Container { + Scale = new Vector2(SPRITE_SCALE), Anchor = Anchor.TopCentre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, @@ -49,7 +50,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Anchor = Anchor.Centre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-glow"), - Scale = new Vector2(SPRITE_SCALE), Blending = BlendingParameters.Additive, Colour = glowColour, }, @@ -58,40 +58,37 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Anchor = Anchor.Centre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-bottom"), - Scale = new Vector2(SPRITE_SCALE), }, discTop = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-top"), - Scale = new Vector2(SPRITE_SCALE), }, fixedMiddle = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-middle"), - Scale = new Vector2(SPRITE_SCALE), }, spinningMiddle = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-middle2"), - Scale = new Vector2(SPRITE_SCALE), }, } }); if (!(source.FindProvider(s => s.GetTexture("spinner-top") != null) is DefaultLegacySkin)) { - scaleContainer.Add(ApproachCircle = new Sprite + AddInternal(ApproachCircle = new Sprite { - Anchor = Anchor.Centre, + Anchor = Anchor.TopCentre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-approachcircle"), Scale = new Vector2(SPRITE_SCALE * 1.86f), + Y = SPINNER_Y_CENTRE, }); } } @@ -143,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy glow.Alpha = DrawableSpinner.Progress; - scaleContainer.Scale = new Vector2(0.8f + (float)Interpolation.ApplyEasing(Easing.Out, DrawableSpinner.Progress) * 0.2f); + scaleContainer.Scale = new Vector2(SPRITE_SCALE * (0.8f + (float)Interpolation.ApplyEasing(Easing.Out, DrawableSpinner.Progress) * 0.2f)); } } } From 999bf27eae3540067220c61ac64c6c6c7001ab9d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 12:07:00 +0300 Subject: [PATCH 489/520] Remove unnecessary abstraction of `ApproachCircle` property --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs | 2 -- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs | 2 -- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index 82a2598443..ae8d6a61f8 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -31,8 +31,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private Container scaleContainer; - public override Drawable ApproachCircle { get; protected set; } - [BackgroundDependencyLoader] private void load(ISkinSource source) { diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs index 8f162806de..cbe721d21d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs @@ -29,8 +29,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private const float final_metre_height = 692 * SPRITE_SCALE; - public override Drawable ApproachCircle { get; protected set; } - [BackgroundDependencyLoader] private void load(ISkinSource source) { diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 3bbb523cfe..317649785e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected DrawableSpinner DrawableSpinner { get; private set; } - public abstract Drawable ApproachCircle { get; protected set; } + public Drawable ApproachCircle { get; protected set; } private Sprite spin; private Sprite clear; From 3eb088f89a2fd3cc3b6490574a6a6963715ecc3f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 21 Jun 2021 15:30:04 +0200 Subject: [PATCH 490/520] Add low difficulty overlaps check --- .../Edit/Checks/CheckLowDiffOverlaps.cs | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs new file mode 100644 index 0000000000..488bdfd972 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs @@ -0,0 +1,107 @@ +// 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 osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Checks +{ + public class CheckLowDiffOverlaps : ICheck + { + // For the lowest difficulties, the osu! Ranking Criteria encourages overlapping ~180 BPM 1/2, but discourages ~180 BPM 1/1. + private const double should_overlap_threshold = 150; // 200 BPM 1/2 + private const double should_probably_overlap_threshold = 175; // 170 BPM 1/2 + private const double should_not_overlap_threshold = 250; // 120 BPM 1/2 = 240 BPM 1/1 + + // Objects need to overlap this much before being treated as an overlap, else it may just be the borders slightly touching. + private const double overlap_leniency = 5; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Spread, "Missing or unexpected overlaps"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateShouldOverlap(this), + new IssueTemplateShouldProbablyOverlap(this), + new IssueTemplateShouldNotOverlap(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + // TODO: This should also apply to *lowest difficulty* Normals - they are skipped for now. + if (context.InterpretedDifficulty > DifficultyRating.Easy) + yield break; + + var hitObjects = context.Beatmap.HitObjects; + + for (int i = 0; i < hitObjects.Count - 1; ++i) + { + if (!(hitObjects[i] is OsuHitObject hitObject) || hitObject is Spinner) + continue; + + if (!(hitObjects[i + 1] is OsuHitObject nextHitObject) || nextHitObject is Spinner) + continue; + + var deltaTime = nextHitObject.StartTime - hitObject.GetEndTime(); + if (deltaTime >= hitObject.TimeFadeIn + hitObject.TimePreempt) + // The objects are not visible at the same time (without mods), hence skipping. + continue; + + var distanceSq = (hitObject.StackedEndPosition - nextHitObject.StackedPosition).LengthSquared; + var diameter = (hitObject.Radius - overlap_leniency) * 2; + var diameterSq = diameter * diameter; + + bool areOverlapping = distanceSq < diameterSq; + + // Slider ends do not need to be overlapped because of slider leniency. + if (!areOverlapping && !(hitObject is Slider)) + { + if (deltaTime < should_overlap_threshold) + yield return new IssueTemplateShouldOverlap(this).Create(deltaTime, hitObject, nextHitObject); + else if (deltaTime < should_probably_overlap_threshold) + yield return new IssueTemplateShouldProbablyOverlap(this).Create(deltaTime, hitObject, nextHitObject); + } + + if (areOverlapping && deltaTime > should_not_overlap_threshold) + yield return new IssueTemplateShouldNotOverlap(this).Create(deltaTime, hitObject, nextHitObject); + } + } + + public abstract class IssueTemplateOverlap : IssueTemplate + { + protected IssueTemplateOverlap(ICheck check, IssueType issueType, string unformattedMessage) + : base(check, issueType, unformattedMessage) + { + } + + public Issue Create(double deltaTime, params HitObject[] hitObjects) => new Issue(hitObjects, this, deltaTime); + } + + public class IssueTemplateShouldOverlap : IssueTemplateOverlap + { + public IssueTemplateShouldOverlap(ICheck check) + : base(check, IssueType.Problem, "These are {0} ms apart and so should be overlapping.") + { + } + } + + public class IssueTemplateShouldProbablyOverlap : IssueTemplateOverlap + { + public IssueTemplateShouldProbablyOverlap(ICheck check) + : base(check, IssueType.Warning, "These are {0} ms apart and so should probably be overlapping.") + { + } + } + + public class IssueTemplateShouldNotOverlap : IssueTemplateOverlap + { + public IssueTemplateShouldNotOverlap(ICheck check) + : base(check, IssueType.Problem, "These are {0} ms apart and so should NOT be overlapping.") + { + } + } + } +} From fcb918d0e11868c7bfd3954939b2143165bf4e45 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 21 Jun 2021 15:30:23 +0200 Subject: [PATCH 491/520] Add time distance equality check --- .../Edit/Checks/CheckTimeDistanceEquality.cs | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs new file mode 100644 index 0000000000..db48878dd3 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs @@ -0,0 +1,157 @@ +// 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.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Checks +{ + public class CheckTimeDistanceEquality : ICheck + { + private const double pattern_lifetime = 600; // Two objects this many ms apart or more are skipped. (200 BPM 2/1) + private const double stack_leniency = 12; // Two objects this distance apart or less are skipped. + + private const double observation_lifetime = 4000; // How long an observation is relevant for comparison. (120 BPM 8/1) + private const double similar_time_leniency = 16; // How different two delta times can be to still be compared. (240 BPM 1/16) + + private const double distance_leniency_absolute_warning = 10; // How many pixels are subtracted from the difference between current and expected distance. + private const double distance_leniency_percent_warning = 0.15; // How much of the current distance that the difference can make out. + private const double distance_leniency_absolute_problem = 20; + private const double distance_leniency_percent_problem = 0.3; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Spread, "Object too close or far away from previous"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateIrregularSpacingProblem(this), + new IssueTemplateIrregularSpacingWarning(this) + }; + + /// + /// Represents an observation of the time and distance between two objects. + /// + private readonly struct ObservedTimeDistance + { + public readonly double ObservationTime; + public readonly double DeltaTime; + public readonly double Distance; + + public ObservedTimeDistance(double observationTime, double deltaTime, double distance) + { + ObservationTime = observationTime; + DeltaTime = deltaTime; + Distance = distance; + } + } + + public IEnumerable Run(BeatmapVerifierContext context) + { + if (context.InterpretedDifficulty > DifficultyRating.Normal) + yield break; + + var prevObservedTimeDistances = new List(); + var hitObjects = context.Beatmap.HitObjects; + + for (int i = 0; i < hitObjects.Count - 1; ++i) + { + if (!(hitObjects[i] is OsuHitObject hitObject) || hitObject is Spinner) + continue; + + if (!(hitObjects[i + 1] is OsuHitObject nextHitObject) || nextHitObject is Spinner) + continue; + + var deltaTime = nextHitObject.StartTime - hitObject.GetEndTime(); + + // Ignore objects that are far enough apart in time to not be considered the same pattern. + if (deltaTime > pattern_lifetime) + continue; + + // Relying on FastInvSqrt is probably good enough here. We'll be taking the difference between distances later, hence square not being sufficient. + var distance = (hitObject.StackedEndPosition - nextHitObject.StackedPosition).LengthFast; + + // Ignore stacks and half-stacks, as these are close enough to where they can't be confused for being time-distanced. + if (distance < stack_leniency) + continue; + + var observedTimeDistance = new ObservedTimeDistance(nextHitObject.StartTime, deltaTime, distance); + var expectedDistance = getExpectedDistance(prevObservedTimeDistances, observedTimeDistance); + + if (expectedDistance == 0) + { + // There was nothing relevant to compare to. + prevObservedTimeDistances.Add(observedTimeDistance); + continue; + } + + if ((Math.Abs(expectedDistance - distance) - distance_leniency_absolute_problem) / distance > distance_leniency_percent_problem) + yield return new IssueTemplateIrregularSpacingProblem(this).Create(expectedDistance, distance, hitObject, nextHitObject); + else if ((Math.Abs(expectedDistance - distance) - distance_leniency_absolute_warning) / distance > distance_leniency_percent_warning) + yield return new IssueTemplateIrregularSpacingWarning(this).Create(expectedDistance, distance, hitObject, nextHitObject); + else + { + // We use `else` here to prevent issues from cascading; an object spaced too far could cause regular spacing to be considered "too short" otherwise. + prevObservedTimeDistances.Add(observedTimeDistance); + } + } + } + + private double getExpectedDistance(IEnumerable prevObservedTimeDistances, ObservedTimeDistance observedTimeDistance) + { + var observations = prevObservedTimeDistances.Count(); + + int count = 0; + double sum = 0; + + // Looping this in reverse allows us to break before going through all elements, as we're only interested in the most recent ones. + for (int i = observations - 1; i >= 0; --i) + { + var prevObservedTimeDistance = prevObservedTimeDistances.ElementAt(i); + + // Only consider observations within the last few seconds - this allows the map to build spacing up/down over time, but prevents it from being too sudden. + if (observedTimeDistance.ObservationTime - prevObservedTimeDistance.ObservationTime > observation_lifetime) + break; + + // Only consider observations which have a similar time difference - this leniency allows handling of multi-BPM maps which speed up/down slowly. + if (Math.Abs(observedTimeDistance.DeltaTime - prevObservedTimeDistance.DeltaTime) > similar_time_leniency) + break; + + count += 1; + sum += prevObservedTimeDistance.Distance / Math.Max(prevObservedTimeDistance.DeltaTime, 1); + } + + return sum / Math.Max(count, 1) * observedTimeDistance.DeltaTime; + } + + public abstract class IssueTemplateIrregularSpacing : IssueTemplate + { + protected IssueTemplateIrregularSpacing(ICheck check, IssueType issueType) + : base(check, issueType, "Expected {0:0} px spacing like previous objects, currently {1:0}.") + { + } + + public Issue Create(double expected, double actual, params HitObject[] hitObjects) => new Issue(hitObjects, this, expected, actual); + } + + public class IssueTemplateIrregularSpacingProblem : IssueTemplateIrregularSpacing + { + public IssueTemplateIrregularSpacingProblem(ICheck check) + : base(check, IssueType.Problem) + { + } + } + + public class IssueTemplateIrregularSpacingWarning : IssueTemplateIrregularSpacing + { + public IssueTemplateIrregularSpacingWarning(ICheck check) + : base(check, IssueType.Warning) + { + } + } + } +} From 2f3f4f3e4b8b660b05d6652a9b1261f09901bcea Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 21 Jun 2021 15:30:45 +0200 Subject: [PATCH 492/520] Add new checks to verifier --- osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 04e881fbf3..896e904f3f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -13,7 +13,12 @@ namespace osu.Game.Rulesets.Osu.Edit { private readonly List checks = new List { - new CheckOffscreenObjects() + // Compose + new CheckOffscreenObjects(), + + // Spread + new CheckTimeDistanceEquality(), + new CheckLowDiffOverlaps() }; public IEnumerable Run(BeatmapVerifierContext context) From e11139eadfe1a6d0e09e247abc02915fede9f21f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 21 Jun 2021 15:33:50 +0200 Subject: [PATCH 493/520] Add low difficulty overlap tests Moq is introduced to mock sliders' end time/position. This is already used similarly in `osu.Game.Tests`. --- .../Editor/Checks/CheckLowDiffOverlapsTest.cs | 260 ++++++++++++++++++ .../osu.Game.Rulesets.Osu.Tests.csproj | 1 + 2 files changed, 261 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckLowDiffOverlapsTest.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckLowDiffOverlapsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckLowDiffOverlapsTest.cs new file mode 100644 index 0000000000..fd17d11d10 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckLowDiffOverlapsTest.cs @@ -0,0 +1,260 @@ +// 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 Moq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Edit.Checks; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks +{ + [TestFixture] + public class CheckLowDiffOverlapsTest + { + private CheckLowDiffOverlaps check; + + [SetUp] + public void Setup() + { + check = new CheckLowDiffOverlaps(); + } + + [Test] + public void TestNoOverlapFarApart() + { + assertOk(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(200, 0) } + } + }); + } + + [Test] + public void TestNoOverlapClose() + { + assertShouldProbablyOverlap(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 167, Position = new Vector2(200, 0) } + } + }); + } + + [Test] + public void TestNoOverlapTooClose() + { + assertShouldOverlap(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 100, Position = new Vector2(200, 0) } + } + }); + } + + [Test] + public void TestNoOverlapTooCloseExpert() + { + assertOk(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 100, Position = new Vector2(200, 0) } + } + }, DifficultyRating.Expert); + } + + [Test] + public void TestOverlapClose() + { + assertOk(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 167, Position = new Vector2(20, 0) } + } + }); + } + + [Test] + public void TestOverlapFarApart() + { + assertShouldNotOverlap(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(20, 0) } + } + }); + } + + [Test] + public void TestAlmostOverlapFarApart() + { + assertOk(new Beatmap + { + HitObjects = new List + { + // Default circle diameter is 128 px, but part of that is the fade/border of the circle. + // We want this to only be a problem when it actually looks like an overlap. + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(125, 0) } + } + }); + } + + [Test] + public void TestAlmostNotOverlapFarApart() + { + assertShouldNotOverlap(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(110, 0) } + } + }); + } + + [Test] + public void TestOverlapFarApartExpert() + { + assertOk(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(20, 0) } + } + }, DifficultyRating.Expert); + } + + [Test] + public void TestOverlapTooFarApart() + { + // Far apart enough to where the objects are not visible at the same time, and so overlapping is fine. + assertOk(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 2000, Position = new Vector2(20, 0) } + } + }); + } + + [Test] + public void TestSliderTailOverlapFarApart() + { + assertShouldNotOverlap(new Beatmap + { + HitObjects = new List + { + getSliderMock(startTime: 0, endTime: 500, startPosition: new Vector2(0, 0), endPosition: new Vector2(100, 0)).Object, + new HitCircle { StartTime = 1000, Position = new Vector2(120, 0) } + } + }); + } + + [Test] + public void TestSliderTailOverlapClose() + { + assertOk(new Beatmap + { + HitObjects = new List + { + getSliderMock(startTime: 0, endTime: 900, startPosition: new Vector2(0, 0), endPosition: new Vector2(100, 0)).Object, + new HitCircle { StartTime = 1000, Position = new Vector2(120, 0) } + } + }); + } + + [Test] + public void TestSliderTailNoOverlapFarApart() + { + assertOk(new Beatmap + { + HitObjects = new List + { + getSliderMock(startTime: 0, endTime: 500, startPosition: new Vector2(0, 0), endPosition: new Vector2(100, 0)).Object, + new HitCircle { StartTime = 1000, Position = new Vector2(300, 0) } + } + }); + } + + [Test] + public void TestSliderTailNoOverlapClose() + { + // If these were circles they would need to overlap, but overlapping with slider tails is not required. + assertOk(new Beatmap + { + HitObjects = new List + { + getSliderMock(startTime: 0, endTime: 900, startPosition: new Vector2(0, 0), endPosition: new Vector2(100, 0)).Object, + new HitCircle { StartTime = 1000, Position = new Vector2(300, 0) } + } + }); + } + + private Mock getSliderMock(double startTime, double endTime, Vector2 startPosition, Vector2 endPosition) + { + var mockSlider = new Mock(); + mockSlider.SetupGet(s => s.StartTime).Returns(startTime); + mockSlider.SetupGet(s => s.Position).Returns(startPosition); + mockSlider.SetupGet(s => s.EndPosition).Returns(endPosition); + mockSlider.As().Setup(d => d.EndTime).Returns(endTime); + + return mockSlider; + } + + private void assertOk(IBeatmap beatmap, DifficultyRating difficultyRating = DifficultyRating.Easy) + { + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating); + Assert.That(check.Run(context), Is.Empty); + } + + private void assertShouldProbablyOverlap(IBeatmap beatmap, int count = 1) + { + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckLowDiffOverlaps.IssueTemplateShouldProbablyOverlap)); + } + + private void assertShouldOverlap(IBeatmap beatmap, int count = 1) + { + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckLowDiffOverlaps.IssueTemplateShouldOverlap)); + } + + private void assertShouldNotOverlap(IBeatmap beatmap, int count = 1) + { + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckLowDiffOverlaps.IssueTemplateShouldNotOverlap)); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index ebe642803b..1efd19f49d 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -3,6 +3,7 @@ + From 629c98e6a0d64e4a0dfb97ca1f876b5252e2d5dd Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 21 Jun 2021 15:34:11 +0200 Subject: [PATCH 494/520] Add time distance equality tests --- .../Checks/CheckTimeDistanceEqualityTest.cs | 324 ++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTimeDistanceEqualityTest.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTimeDistanceEqualityTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTimeDistanceEqualityTest.cs new file mode 100644 index 0000000000..49a6fd12fa --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTimeDistanceEqualityTest.cs @@ -0,0 +1,324 @@ +// 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 Moq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Edit.Checks; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks +{ + [TestFixture] + public class CheckTimeDistanceEqualityTest + { + private CheckTimeDistanceEquality check; + + [SetUp] + public void Setup() + { + check = new CheckTimeDistanceEquality(); + } + + [Test] + public void TestCirclesEquidistant() + { + assertOk(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + new HitCircle { StartTime = 1000, Position = new Vector2(100, 0) }, + new HitCircle { StartTime = 1500, Position = new Vector2(150, 0) } + } + }); + } + + [Test] + public void TestCirclesOneSlightlyOff() + { + assertWarning(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + new HitCircle { StartTime = 1000, Position = new Vector2(80, 0) }, // Distance a quite low compared to previous. + new HitCircle { StartTime = 1500, Position = new Vector2(130, 0) } + } + }); + } + + [Test] + public void TestCirclesOneOff() + { + assertProblem(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + new HitCircle { StartTime = 1000, Position = new Vector2(150, 0) }, // Twice the regular spacing. + new HitCircle { StartTime = 1500, Position = new Vector2(100, 0) } + } + }); + } + + [Test] + public void TestCirclesTwoOff() + { + assertProblem(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + new HitCircle { StartTime = 1000, Position = new Vector2(150, 0) }, // Twice the regular spacing. + new HitCircle { StartTime = 1500, Position = new Vector2(250, 0) } // Also twice the regular spacing. + } + }, count: 2); + } + + [Test] + public void TestCirclesStacked() + { + assertOk(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + new HitCircle { StartTime = 1000, Position = new Vector2(50, 0) }, // Stacked, is fine. + new HitCircle { StartTime = 1500, Position = new Vector2(100, 0) } + } + }); + } + + [Test] + public void TestCirclesStacking() + { + assertWarning(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + new HitCircle { StartTime = 1000, Position = new Vector2(50, 0), StackHeight = 1 }, + new HitCircle { StartTime = 1500, Position = new Vector2(50, 0), StackHeight = 2 }, + new HitCircle { StartTime = 2000, Position = new Vector2(50, 0), StackHeight = 3 }, + new HitCircle { StartTime = 2500, Position = new Vector2(50, 0), StackHeight = 4 }, // Ends up far from (50; 0), causing irregular spacing. + new HitCircle { StartTime = 3000, Position = new Vector2(100, 0) } + } + }); + } + + [Test] + public void TestCirclesHalfStack() + { + assertOk(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + new HitCircle { StartTime = 1000, Position = new Vector2(55, 0) }, // Basically stacked, so is fine. + new HitCircle { StartTime = 1500, Position = new Vector2(105, 0) } + } + }); + } + + [Test] + public void TestCirclesPartialOverlap() + { + assertProblem(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + new HitCircle { StartTime = 1000, Position = new Vector2(65, 0) }, // Really low distance compared to previous. + new HitCircle { StartTime = 1500, Position = new Vector2(115, 0) } + } + }); + } + + [Test] + public void TestCirclesSlightlyDifferent() + { + assertOk(new Beatmap + { + HitObjects = new List + { + // Does not need to be perfect, as long as the distance is approximately correct it's sight-readable. + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(52, 0) }, + new HitCircle { StartTime = 1000, Position = new Vector2(97, 0) }, + new HitCircle { StartTime = 1500, Position = new Vector2(165, 0) } + } + }); + } + + [Test] + public void TestCirclesSlowlyChanging() + { + const float multiplier = 1.2f; + + assertOk(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + new HitCircle { StartTime = 1000, Position = new Vector2(50 + 50 * multiplier, 0) }, + // This gap would be a warning if it weren't for the previous pushing the average spacing up. + new HitCircle { StartTime = 1500, Position = new Vector2(50 + 50 * multiplier + 50 * multiplier * multiplier, 0) } + } + }); + } + + [Test] + public void TestCirclesQuicklyChanging() + { + const float multiplier = 1.6f; + + var beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + new HitCircle { StartTime = 1000, Position = new Vector2(50 + 50 * multiplier, 0) }, // Warning + new HitCircle { StartTime = 1500, Position = new Vector2(50 + 50 * multiplier + 50 * multiplier * multiplier, 0) } // Problem + } + }; + + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(2)); + Assert.That(issues.First().Template is CheckTimeDistanceEquality.IssueTemplateIrregularSpacingWarning); + Assert.That(issues.Last().Template is CheckTimeDistanceEquality.IssueTemplateIrregularSpacingProblem); + } + + [Test] + public void TestCirclesTooFarApart() + { + assertOk(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + new HitCircle { StartTime = 4000, Position = new Vector2(200, 0) }, // 2 seconds apart from previous, so can start from wherever. + new HitCircle { StartTime = 4500, Position = new Vector2(250, 0) } + } + }); + } + + [Test] + public void TestCirclesOneOffExpert() + { + assertOk(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + new HitCircle { StartTime = 1000, Position = new Vector2(150, 0) }, // Jumps are allowed in higher difficulties. + new HitCircle { StartTime = 1500, Position = new Vector2(100, 0) } + } + }, DifficultyRating.Expert); + } + + [Test] + public void TestSpinner() + { + assertOk(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + new Spinner { StartTime = 500, EndTime = 1000 }, // Distance to and from the spinner should be ignored. If it isn't this should give a problem. + new HitCircle { StartTime = 1500, Position = new Vector2(100, 0) }, + new HitCircle { StartTime = 2000, Position = new Vector2(150, 0) } + } + }); + } + + [Test] + public void TestSliders() + { + assertOk(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + getSliderMock(startTime: 1000, endTime: 1500, startPosition: new Vector2(100, 0), endPosition: new Vector2(150, 0)).Object, + getSliderMock(startTime: 2000, endTime: 2500, startPosition: new Vector2(200, 0), endPosition: new Vector2(250, 0)).Object, + new HitCircle { StartTime = 2500, Position = new Vector2(300, 0) } + } + }); + } + + [Test] + public void TestSlidersOneOff() + { + assertProblem(new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, Position = new Vector2(0) }, + new HitCircle { StartTime = 500, Position = new Vector2(50, 0) }, + getSliderMock(startTime: 1000, endTime: 1500, startPosition: new Vector2(100, 0), endPosition: new Vector2(150, 0)).Object, + getSliderMock(startTime: 2000, endTime: 2500, startPosition: new Vector2(250, 0), endPosition: new Vector2(300, 0)).Object, // Twice the spacing. + new HitCircle { StartTime = 2500, Position = new Vector2(300, 0) } + } + }); + } + + private Mock getSliderMock(double startTime, double endTime, Vector2 startPosition, Vector2 endPosition) + { + var mockSlider = new Mock(); + mockSlider.SetupGet(s => s.StartTime).Returns(startTime); + mockSlider.SetupGet(s => s.Position).Returns(startPosition); + mockSlider.SetupGet(s => s.EndPosition).Returns(endPosition); + mockSlider.As().Setup(d => d.EndTime).Returns(endTime); + + return mockSlider; + } + + private void assertOk(IBeatmap beatmap, DifficultyRating difficultyRating = DifficultyRating.Easy) + { + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating); + Assert.That(check.Run(context), Is.Empty); + } + + private void assertWarning(IBeatmap beatmap, int count = 1) + { + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckTimeDistanceEquality.IssueTemplateIrregularSpacingWarning)); + } + + private void assertProblem(IBeatmap beatmap, int count = 1) + { + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckTimeDistanceEquality.IssueTemplateIrregularSpacingProblem)); + } + } +} From 36e459e97e7b516f2c95a481c1561488226289a5 Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Mon, 21 Jun 2021 13:42:15 -0700 Subject: [PATCH 495/520] Use margin instead of padding --- osu.Game/Overlays/OverlayStreamItem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/OverlayStreamItem.cs b/osu.Game/Overlays/OverlayStreamItem.cs index 7f8559e7de..cd1391a3d8 100644 --- a/osu.Game/Overlays/OverlayStreamItem.cs +++ b/osu.Game/Overlays/OverlayStreamItem.cs @@ -39,9 +39,9 @@ namespace osu.Game.Overlays protected OverlayStreamItem(T value) : base(value) { - Height = 60; - Width = 100; - Padding = new MarginPadding(5); + Height = 50; + Width = 90; + Margin = new MarginPadding(5); } [BackgroundDependencyLoader] From ebe0d43790a7d793dd6db2cb2ae8e1ce57d6d234 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 02:51:00 +0300 Subject: [PATCH 496/520] Add ability to disallow falling back to parent skins --- osu.Game/Skinning/SkinProvidingContainer.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 315571e79b..4435d924c2 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -38,6 +38,11 @@ namespace osu.Game.Skinning [CanBeNull] private ISkinSource fallbackSource; + /// + /// Whether falling back to parent s is allowed in this container. + /// + protected virtual bool AllowFallingBackToParent => true; + protected virtual bool AllowDrawableLookup(ISkinComponent component) => true; protected virtual bool AllowTextureLookup(string componentName) => true; @@ -180,9 +185,12 @@ namespace osu.Game.Skinning { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - fallbackSource = dependencies.Get(); - if (fallbackSource != null) - fallbackSource.SourceChanged += OnSourceChanged; + if (AllowFallingBackToParent) + { + fallbackSource = dependencies.Get(); + if (fallbackSource != null) + fallbackSource.SourceChanged += OnSourceChanged; + } dependencies.CacheAs(this); From d53a43cf3c65dbc1ede229e1dffa56f32f6a87da Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 02:52:37 +0300 Subject: [PATCH 497/520] Isolate `RulesetSkinProvidingContainer` from falling back to parent skin sources For simplicity of lookup order, and which sources are used for the lookup. --- .../Skinning/RulesetSkinProvidingContainer.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index bbaeee98d8..54d366b2f4 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -7,18 +7,26 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.UI; namespace osu.Game.Skinning { /// - /// A type of that provides access to the beatmap skin and user skin, - /// with each legacy skin source transformed with the ruleset's legacy skin transformer. + /// A type of specialized for and other gameplay-related components. + /// Providing access to the skin sources and the beatmap skin each surrounded with the ruleset legacy skin transformer. + /// While also limiting lookups from falling back to any parent s out of this container. /// public class RulesetSkinProvidingContainer : SkinProvidingContainer { protected readonly Ruleset Ruleset; protected readonly IBeatmap Beatmap; + /// + /// This container already re-exposes all skin sources in a ruleset-usable form. + /// Therefore disallow falling back to any parent any further. + /// + protected override bool AllowFallingBackToParent => false; + protected override Container Content { get; } public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) @@ -42,12 +50,7 @@ namespace osu.Game.Skinning private void load() { UpdateSkins(); - } - - protected override void OnSourceChanged() - { - UpdateSkins(); - base.OnSourceChanged(); + skinManager.SourceChanged += UpdateSkins; } protected virtual void UpdateSkins() @@ -83,5 +86,13 @@ namespace osu.Game.Skinning return legacySkin; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (skinManager != null) + skinManager.SourceChanged -= UpdateSkins; + } } } From 97dbc7f20ecc19ee0d00516a51618212b28bc404 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 02:54:34 +0300 Subject: [PATCH 498/520] Add back `SkinManager.DefaultSkin` to the ruleset skin lookup sources --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 54d366b2f4..8113597dee 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -73,6 +73,8 @@ namespace osu.Game.Skinning SkinSources.Add(skinManager.CurrentSkin.Value); break; } + + SkinSources.Add(skinManager.DefaultSkin); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) From 9e5bb146d3aaa2936c07f2299a58ce36d56d82a4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 03:06:39 +0300 Subject: [PATCH 499/520] Add xmldoc to `SkinManager` The `` part comes from `BeatmapManager`, which I believe works correctly here as well, as this does handle the "storage and retrieval" of skins. --- osu.Game/Skinning/SkinManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 7acc52809f..1f10177a9e 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -30,6 +30,13 @@ using osu.Game.IO.Archives; namespace osu.Game.Skinning { + /// + /// Handles the storage and retrieval of s. + /// + /// + /// This is also exposed and cached as on a game-wide level for general components across the game. + /// Lookups from gameplay components are instead covered by , and are never hit here. + /// [ExcludeFromDynamicCompile] public class SkinManager : ArchiveModelManager, ISkinSource, IStorageResourceProvider { From 627c857da8d0293625e9600c2e5176c333c0f0ee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 03:44:32 +0300 Subject: [PATCH 500/520] Propagate `SourceChanged` events from `SkinManager` down in the ruleset skin container --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8113597dee..4b3c3881c2 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -50,7 +50,13 @@ namespace osu.Game.Skinning private void load() { UpdateSkins(); - skinManager.SourceChanged += UpdateSkins; + skinManager.SourceChanged += OnSourceChanged; + } + + protected override void OnSourceChanged() + { + UpdateSkins(); + base.OnSourceChanged(); } protected virtual void UpdateSkins() From caa90bccc6c92ecc399900ad4774f308002c1260 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 03:45:43 +0300 Subject: [PATCH 501/520] Fix default skin potentially added twice in `RulesetSkinProvidingContainer` --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 4b3c3881c2..88cf70fa18 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -80,7 +80,8 @@ namespace osu.Game.Skinning break; } - SkinSources.Add(skinManager.DefaultSkin); + if (skinManager.CurrentSkin.Value != skinManager.DefaultSkin) + SkinSources.Add(skinManager.DefaultSkin); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) From ec040ff3fc80e3f0374245564f16279caf547b3f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 05:05:41 +0300 Subject: [PATCH 502/520] Fix leak due to not properly unbinding `SourceChanged` event on disposal --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 88cf70fa18..b07d3f5199 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); if (skinManager != null) - skinManager.SourceChanged -= UpdateSkins; + skinManager.SourceChanged -= OnSourceChanged; } } } From f89c154e1810b54b14f48522c1c5c2a6b5bcb546 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 22 Jun 2021 12:24:25 +0700 Subject: [PATCH 503/520] change `GetFontSizeByLevel` to return actual font size --- .../Containers/Markdown/OsuMarkdownHeading.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs index 40eb4cad15..a07216c32e 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs @@ -28,27 +28,25 @@ namespace osu.Game.Graphics.Containers.Markdown // Reference for this font size // https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/bem/osu-md.less#L9 // https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/variables.less#L161 - const float base_font_size = 14; - switch (level) { case 1: - return 30 / base_font_size; + return 30; case 2: - return 26 / base_font_size; + return 26; case 3: - return 20 / base_font_size; + return 20; case 4: - return 18 / base_font_size; + return 18; case 5: - return 16 / base_font_size; + return 16; default: - return 1; + return 14; } } From 5c3129f1a2e83a58e2c19a6018fc02df9544530c Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 22 Jun 2021 12:24:51 +0700 Subject: [PATCH 504/520] add font size in `HeadingTextFlowContainer` --- .../Graphics/Containers/Markdown/OsuMarkdownHeading.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs index a07216c32e..a3a86df678 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs @@ -20,7 +20,8 @@ namespace osu.Game.Graphics.Containers.Markdown public override MarkdownTextFlowContainer CreateTextFlow() => new HeadingTextFlowContainer { - Weight = GetFontWeightByLevel(level), + FontSize = GetFontSizeByLevel(level), + FontWeight = GetFontWeightByLevel(level), }; protected override float GetFontSizeByLevel(int level) @@ -65,9 +66,11 @@ namespace osu.Game.Graphics.Containers.Markdown private class HeadingTextFlowContainer : OsuMarkdownTextFlowContainer { - public FontWeight Weight { get; set; } + public float FontSize; + public FontWeight FontWeight; - protected override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(weight: Weight)); + protected override SpriteText CreateSpriteText() + => base.CreateSpriteText().With(t => t.Font = t.Font.With(size: FontSize, weight: FontWeight)); } } } From e9339d6100b43c72308862582215c00ff3a2950d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 15:16:19 +0900 Subject: [PATCH 505/520] Move some inline comments on `const`s to xmldoc instead --- .../Edit/Checks/CheckLowDiffOverlaps.cs | 4 ++- .../Edit/Checks/CheckTimeDistanceEquality.cs | 34 +++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs index 488bdfd972..1dd859b5b8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs @@ -17,7 +17,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks private const double should_probably_overlap_threshold = 175; // 170 BPM 1/2 private const double should_not_overlap_threshold = 250; // 120 BPM 1/2 = 240 BPM 1/1 - // Objects need to overlap this much before being treated as an overlap, else it may just be the borders slightly touching. + /// + /// Objects need to overlap this much before being treated as an overlap, else it may just be the borders slightly touching. + /// private const double overlap_leniency = 5; public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Spread, "Missing or unexpected overlaps"); diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs index db48878dd3..6420d9558e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs @@ -14,14 +14,36 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks { public class CheckTimeDistanceEquality : ICheck { - private const double pattern_lifetime = 600; // Two objects this many ms apart or more are skipped. (200 BPM 2/1) - private const double stack_leniency = 12; // Two objects this distance apart or less are skipped. + /// + /// Two objects this many ms apart or more are skipped. (200 BPM 2/1) + /// + private const double pattern_lifetime = 600; - private const double observation_lifetime = 4000; // How long an observation is relevant for comparison. (120 BPM 8/1) - private const double similar_time_leniency = 16; // How different two delta times can be to still be compared. (240 BPM 1/16) + /// + /// Two objects this distance apart or less are skipped. + /// + private const double stack_leniency = 12; + + /// + /// How long an observation is relevant for comparison. (120 BPM 8/1) + /// + private const double observation_lifetime = 4000; + + /// + /// How different two delta times can be to still be compared. (240 BPM 1/16) + /// + private const double similar_time_leniency = 16; + + /// + /// How many pixels are subtracted from the difference between current and expected distance. + /// + private const double distance_leniency_absolute_warning = 10; + + /// + /// How much of the current distance that the difference can make out. + /// + private const double distance_leniency_percent_warning = 0.15; - private const double distance_leniency_absolute_warning = 10; // How many pixels are subtracted from the difference between current and expected distance. - private const double distance_leniency_percent_warning = 0.15; // How much of the current distance that the difference can make out. private const double distance_leniency_absolute_problem = 20; private const double distance_leniency_percent_problem = 0.3; From b54e82eb993d2c41d0f0b98b094c8188414cdf05 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 12:34:34 +0900 Subject: [PATCH 506/520] Remove unused argument from `CatchPlayfield` --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 3 +-- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 0e1ef90737..644facdabc 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.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 System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI // only check the X position; handle all vertical space. base.ReceivePositionalInputAt(new Vector2(screenSpacePos.X, ScreenSpaceDrawQuad.Centre.Y)); - public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation) + public CatchPlayfield(BeatmapDifficulty difficulty) { var droppedObjectContainer = new Container { diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 9389fa803b..8b6a074426 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield); - protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); + protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer(); From 0ad189e357f93d023dbaec929713ad40e867df36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 16:19:55 +0900 Subject: [PATCH 507/520] Expose skin sources via `ISkinSource` and revert to consuming based on hierarchy --- .../TestSceneCursorTrail.cs | 4 +++ .../TestSceneSkinFallbacks.cs | 3 ++ .../Gameplay/TestSceneSkinnableDrawable.cs | 3 ++ .../Gameplay/TestSceneSkinnableSound.cs | 2 ++ osu.Game/Skinning/ISkinSource.cs | 6 ++++ .../Skinning/RulesetSkinProvidingContainer.cs | 35 ++++++++----------- osu.Game/Skinning/SkinManager.cs | 26 ++++++++++---- osu.Game/Skinning/SkinProvidingContainer.cs | 15 ++++++++ .../Beatmaps/LegacyBeatmapSkinColourTest.cs | 1 + 9 files changed, 67 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index 46274e779b..fe962d3cb8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -114,6 +116,8 @@ namespace osu.Game.Rulesets.Osu.Tests public ISkin FindProvider(Func lookupFunction) => null; + public IEnumerable AllSources => Enumerable.Empty(); + public event Action SourceChanged { add { } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 2b45818aa9..5ef27ab5ea 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -146,6 +147,8 @@ namespace osu.Game.Rulesets.Osu.Tests public ISkin FindProvider(Func lookupFunction) => null; + public IEnumerable AllSources => Enumerable.Empty(); + public event Action SourceChanged; private bool enabled = true; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 77966e925a..3317d8f80a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using NUnit.Framework; @@ -330,6 +331,8 @@ namespace osu.Game.Tests.Visual.Gameplay public ISkin FindProvider(Func lookupFunction) => throw new NotImplementedException(); + public IEnumerable AllSources => Enumerable.Empty(); + public event Action SourceChanged { add { } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 55ee01e0d5..59edb527eb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -147,6 +148,7 @@ namespace osu.Game.Tests.Visual.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => source?.GetConfig(lookup); public ISkin FindProvider(Func lookupFunction) => source?.FindProvider(lookupFunction); + public IEnumerable AllSources => source.AllSources; public void TriggerSourceChanged() { diff --git a/osu.Game/Skinning/ISkinSource.cs b/osu.Game/Skinning/ISkinSource.cs index c7ebe91d64..ba3e2bf6ad 100644 --- a/osu.Game/Skinning/ISkinSource.cs +++ b/osu.Game/Skinning/ISkinSource.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using JetBrains.Annotations; namespace osu.Game.Skinning @@ -20,5 +21,10 @@ namespace osu.Game.Skinning /// The skin to be used for subsequent lookups, or null if none is available. [CanBeNull] ISkin FindProvider(Func lookupFunction); + + /// + /// Retrieve all sources available for lookup, with highest priority source first. + /// + IEnumerable AllSources { get; } } } diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index b07d3f5199..21a858977b 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -26,7 +26,6 @@ namespace osu.Game.Skinning /// Therefore disallow falling back to any parent any further. /// protected override bool AllowFallingBackToParent => false; - protected override Container Content { get; } public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) @@ -44,13 +43,13 @@ namespace osu.Game.Skinning } [Resolved] - private SkinManager skinManager { get; set; } + private ISkinSource skinSource { get; set; } [BackgroundDependencyLoader] private void load() { UpdateSkins(); - skinManager.SourceChanged += OnSourceChanged; + skinSource.SourceChanged += OnSourceChanged; } protected override void OnSourceChanged() @@ -63,25 +62,19 @@ namespace osu.Game.Skinning { SkinSources.Clear(); - // TODO: we also want to insert a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements. - - switch (skinManager.CurrentSkin.Value) + foreach (var skin in skinSource.AllSources) { - case LegacySkin currentLegacySkin: - SkinSources.Add(GetLegacyRulesetTransformedSkin(currentLegacySkin)); + switch (skin) + { + case LegacySkin legacySkin: + SkinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); + break; - if (currentLegacySkin != skinManager.DefaultLegacySkin) - SkinSources.Add(GetLegacyRulesetTransformedSkin(skinManager.DefaultLegacySkin)); - - break; - - default: - SkinSources.Add(skinManager.CurrentSkin.Value); - break; + default: + SkinSources.Add(skin); + break; + } } - - if (skinManager.CurrentSkin.Value != skinManager.DefaultSkin) - SkinSources.Add(skinManager.DefaultSkin); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) @@ -100,8 +93,8 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - if (skinManager != null) - skinManager.SourceChanged -= OnSourceChanged; + if (skinSource != null) + skinSource.SourceChanged -= OnSourceChanged; } } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 1f10177a9e..6bd4888eb5 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -248,17 +248,29 @@ namespace osu.Game.Skinning return null; } + public IEnumerable AllSources + { + get + { + yield return CurrentSkin.Value; + + if (CurrentSkin.Value is LegacySkin) + yield return DefaultLegacySkin; + + yield return DefaultSkin; + } + } + private T lookupWithFallback(Func lookupFunction) where T : class { - if (lookupFunction(CurrentSkin.Value) is T skinSourced) - return skinSourced; + foreach (var source in AllSources) + { + if (lookupFunction(source) is T skinSourced) + return skinSourced; + } - if (CurrentSkin.Value is LegacySkin && lookupFunction(DefaultLegacySkin) is T legacySourced) - return legacySourced; - - // Finally fall back to the (non-legacy) default. - return lookupFunction(DefaultSkin); + return null; } #region IResourceStorageProvider diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 4435d924c2..c83c299723 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -131,6 +131,21 @@ namespace osu.Game.Skinning return fallbackSource?.FindProvider(lookupFunction); } + public IEnumerable AllSources + { + get + { + foreach (var skin in SkinSources) + yield return skin; + + if (fallbackSource != null) + { + foreach (var skin in fallbackSource.AllSources) + yield return skin; + } + } + } + public Drawable GetDrawableComponent(ISkinComponent component) { foreach (var skin in SkinSources) diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs index 347b611579..bb4768982a 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -146,6 +146,7 @@ namespace osu.Game.Tests.Beatmaps } public ISkin FindProvider(Func lookupFunction) => null; + public IEnumerable AllSources => Enumerable.Empty(); } } } From 14bdcef26b7da34ae0a1e1376b9c8e859ce42224 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 16:20:09 +0900 Subject: [PATCH 508/520] Add missing newline --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 21a858977b..f1cc3df3de 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -26,6 +26,7 @@ namespace osu.Game.Skinning /// Therefore disallow falling back to any parent any further. /// protected override bool AllowFallingBackToParent => false; + protected override Container Content { get; } public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) From ffac32a848b7755aa5e2f17f912b75946ab08014 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 16:40:48 +0900 Subject: [PATCH 509/520] Reword xmldoc --- osu.Game/Skinning/ISkinnableDrawable.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/ISkinnableDrawable.cs b/osu.Game/Skinning/ISkinnableDrawable.cs index 9625a9eb6d..60b40982e5 100644 --- a/osu.Game/Skinning/ISkinnableDrawable.cs +++ b/osu.Game/Skinning/ISkinnableDrawable.cs @@ -16,8 +16,9 @@ namespace osu.Game.Skinning bool IsEditable => true; /// - /// if this 's is automatically determined by proximity, - /// if the user has chosen a fixed anchor point. + /// In the context of the skin layout editor, whether this has a permanent anchor defined. + /// If , this 's is automatically determined by proximity, + /// If , a fixed anchor point has been defined. /// bool UsesFixedAnchor { get; set; } } From 4b3165084d72b4fefb2fe81ec9fb401da2c5fdc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 16:40:56 +0900 Subject: [PATCH 510/520] Move scoped functionality into local function --- .../Skinning/Editor/SkinSelectionHandler.cs | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index c2ad08f0dc..23f36ffe5b 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -281,23 +281,7 @@ namespace osu.Game.Skinning.Editor if (parent == null) return drawable.Anchor; - Vector2 screenPosition; - { - var quad = drawable.ScreenSpaceDrawQuad; - var origin = drawable.Origin; - - screenPosition = quad.TopLeft; - - if (origin.HasFlagFast(Anchor.x2)) - screenPosition.X += quad.Width; - else if (origin.HasFlagFast(Anchor.x1)) - screenPosition.X += quad.Width / 2f; - - if (origin.HasFlagFast(Anchor.y2)) - screenPosition.Y += quad.Height; - else if (origin.HasFlagFast(Anchor.y1)) - screenPosition.Y += quad.Height / 2f; - } + var screenPosition = getScreenPosition(); var absolutePosition = parent.ToLocalSpace(screenPosition); var factor = parent.RelativeToAbsoluteFactor; @@ -319,6 +303,26 @@ namespace osu.Game.Skinning.Editor result |= getAnchorFromPosition(absolutePosition.Y / factor.Y, Anchor.y0, Anchor.y1, Anchor.y2); return result; + + Vector2 getScreenPosition() + { + var quad = drawable.ScreenSpaceDrawQuad; + var origin = drawable.Origin; + + var pos = quad.TopLeft; + + if (origin.HasFlagFast(Anchor.x2)) + pos.X += quad.Width; + else if (origin.HasFlagFast(Anchor.x1)) + pos.X += quad.Width / 2f; + + if (origin.HasFlagFast(Anchor.y2)) + pos.Y += quad.Height; + else if (origin.HasFlagFast(Anchor.y1)) + pos.Y += quad.Height / 2f; + + return pos; + } } private static void applyAnchor(Drawable drawable, Anchor anchor) From b12adc6073b7d9c493a97bc9b2e8c5172fc46d88 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 10:48:03 +0300 Subject: [PATCH 511/520] Remove all test skinning changes in favour of the `ISkinSource.AllSources` path --- .../TestSceneLegacyBeatmapSkin.cs | 7 +++- .../TestSceneSkinFallbacks.cs | 24 ++++++++++- osu.Game/Screens/Play/Player.cs | 4 +- .../Tests/Beatmaps/HitObjectSampleTest.cs | 42 +++++++++++++++++-- .../Beatmaps/LegacyBeatmapSkinColourTest.cs | 32 ++++++++++---- .../Tests/Visual/LegacySkinPlayerTestScene.cs | 17 +++++++- osu.Game/Tests/Visual/PlayerTestScene.cs | 8 ---- osu.Game/Tests/Visual/TestPlayer.cs | 42 ------------------- 8 files changed, 108 insertions(+), 68 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs index 0077ff9e3c..bc3daca16f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs @@ -101,10 +101,15 @@ namespace osu.Game.Rulesets.Catch.Tests AddAssert("is custom hyper dash fruit colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashFruitColour == TestSkin.HYPER_DASH_FRUIT_COLOUR); } - protected override ExposedPlayer CreateTestPlayer() => new CatchExposedPlayer(); + protected override ExposedPlayer CreateTestPlayer(bool userHasCustomColours) => new CatchExposedPlayer(userHasCustomColours); private class CatchExposedPlayer : ExposedPlayer { + public CatchExposedPlayer(bool userHasCustomColours) + : base(userHasCustomColours) + { + } + public Color4 UsableHyperDashColour => GameplayClockContainer.ChildrenOfType() .First() diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 5ef27ab5ea..334d27e0a9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -22,6 +22,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osu.Game.Storyboards; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { @@ -99,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Resolved] private AudioManager audio { get; set; } - protected override ISkin GetPlayerSkin() => testUserSkin; + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, audio, testBeatmapSkin); @@ -116,6 +117,27 @@ namespace osu.Game.Rulesets.Osu.Tests protected override ISkin GetSkin() => skin; } + public class SkinProvidingPlayer : TestPlayer + { + private readonly TestSource userSkin; + + public SkinProvidingPlayer(TestSource userSkin) + { + this.userSkin = userSkin; + } + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.CacheAs(userSkin); + + return dependencies; + } + } + public class TestSource : ISkinSource { private readonly string identifier; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index efc5fcfbe5..58f60d14cf 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -228,7 +228,7 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(GameplayBeatmap); - var rulesetSkinProvider = CreateRulesetSkinProvider(GameplayRuleset, playableBeatmap, Beatmap.Value.Skin); + var rulesetSkinProvider = new RulesetSkinProvidingContainer(GameplayRuleset, playableBeatmap, Beatmap.Value.Skin); // load the skinning hierarchy first. // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. @@ -309,8 +309,6 @@ namespace osu.Game.Screens.Play protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); - protected virtual RulesetSkinProvidingContainer CreateRulesetSkinProvider(Ruleset ruleset, IBeatmap beatmap, ISkin beatmapSkin) => new RulesetSkinProvidingContainer(ruleset, beatmap, beatmapSkin); - private Drawable createUnderlayComponents() => DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 7af0397726..7ee6c519b7 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -47,11 +47,15 @@ namespace osu.Game.Tests.Beatmaps private readonly TestResourceStore userSkinResourceStore = new TestResourceStore(); private readonly TestResourceStore beatmapSkinResourceStore = new TestResourceStore(); + private SkinSourceDependencyContainer dependencies; private IBeatmap currentTestBeatmap; protected sealed override bool HasCustomSteps => true; protected override bool Autoplay => true; + protected sealed override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + => new DependencyContainer(dependencies = new SkinSourceDependencyContainer(base.CreateChildDependencies(parent))); + protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestBeatmap; protected sealed override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) @@ -59,8 +63,6 @@ namespace osu.Game.Tests.Beatmaps protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); - protected override ISkin GetPlayerSkin() => Skin; - protected void CreateTestWithBeatmap(string filename) { CreateTest(() => @@ -107,7 +109,8 @@ namespace osu.Game.Tests.Beatmaps } }; - Skin = new LegacySkin(userSkinInfo, this); + // Need to refresh the cached skin source to refresh the skin resource store. + dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, this)); }); } @@ -129,6 +132,39 @@ namespace osu.Game.Tests.Beatmaps #endregion + private class SkinSourceDependencyContainer : IReadOnlyDependencyContainer + { + public ISkinSource SkinSource; + + private readonly IReadOnlyDependencyContainer fallback; + + public SkinSourceDependencyContainer(IReadOnlyDependencyContainer fallback) + { + this.fallback = fallback; + } + + public object Get(Type type) + { + if (type == typeof(ISkinSource)) + return SkinSource; + + return fallback.Get(type); + } + + public object Get(Type type, CacheInfo info) + { + if (type == typeof(ISkinSource)) + return SkinSource; + + return fallback.Get(type, info); + } + + public void Inject(T instance) where T : class + { + // Never used directly + } + } + private class TestResourceStore : IResourceStore { public readonly List PerformedLookups = new List(); diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs index bb4768982a..86c75c297a 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Screens.Play; using osu.Game.Skinning; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -47,24 +49,36 @@ namespace osu.Game.Tests.Beatmaps protected virtual ExposedPlayer LoadBeatmap(bool userHasCustomColours) { + ExposedPlayer player; + Beatmap.Value = testBeatmap; - ExposedPlayer player = CreateTestPlayer(); - - player.SetSkin(new TestSkin(userHasCustomColours)); - - LoadScreen(player); + LoadScreen(player = CreateTestPlayer(userHasCustomColours)); return player; } - protected virtual ExposedPlayer CreateTestPlayer() => new ExposedPlayer(); + protected virtual ExposedPlayer CreateTestPlayer(bool userHasCustomColours) => new ExposedPlayer(userHasCustomColours); - protected class ExposedPlayer : TestPlayer + protected class ExposedPlayer : Player { - public ExposedPlayer() - : base(false, false) + protected readonly bool UserHasCustomColours; + + public ExposedPlayer(bool userHasCustomColours) + : base(new PlayerConfiguration + { + AllowPause = false, + ShowResults = false, + }) { + UserHasCustomColours = userHasCustomColours; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(new TestSkin(UserHasCustomColours)); + return dependencies; } public IReadOnlyList UsableComboColours => diff --git a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs index f4f351d46c..d74be70df8 100644 --- a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs +++ b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; +using osu.Game.Rulesets; using osu.Game.Skinning; namespace osu.Game.Tests.Visual @@ -15,12 +16,15 @@ namespace osu.Game.Tests.Visual { protected LegacySkin LegacySkin { get; private set; } - protected override ISkin GetPlayerSkin() => LegacySkin; + private ISkinSource legacySkinSource; + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(legacySkinSource); [BackgroundDependencyLoader] private void load(SkinManager skins) { LegacySkin = new DefaultLegacySkin(skins); + legacySkinSource = new SkinProvidingContainer(LegacySkin); } [SetUpSteps] @@ -47,5 +51,16 @@ namespace osu.Game.Tests.Visual AddUntilStep("wait for components to load", () => this.ChildrenOfType().All(t => t.ComponentsLoaded)); } + + public class SkinProvidingPlayer : TestPlayer + { + [Cached(typeof(ISkinSource))] + private readonly ISkinSource skinSource; + + public SkinProvidingPlayer(ISkinSource skinSource) + { + this.skinSource = skinSource; + } + } } } diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index f5fad895e2..088e997de9 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -10,7 +10,6 @@ using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Skinning; namespace osu.Game.Tests.Visual { @@ -79,8 +78,6 @@ namespace osu.Game.Tests.Visual } Player = CreatePlayer(ruleset); - Player.SetSkin(GetPlayerSkin()); - LoadScreen(Player); } @@ -96,11 +93,6 @@ namespace osu.Game.Tests.Visual [NotNull] protected abstract Ruleset CreatePlayerRuleset(); - /// - /// Creates an to be put inside the 's ruleset skin providing container. - /// - protected virtual ISkin GetPlayerSkin() => null; - protected sealed override Ruleset CreateRuleset() => CreatePlayerRuleset(); protected virtual TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 19fd7068b9..09da4db952 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -1,21 +1,15 @@ // 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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; -using osu.Game.Skinning; namespace osu.Game.Tests.Visual { @@ -80,41 +74,5 @@ namespace osu.Game.Tests.Visual { ScoreProcessor.NewJudgement += r => Results.Add(r); } - - public ISkin Skin { get; private set; } - - private TestSkinProvidingContainer rulesetSkinProvider; - - internal void SetSkin(ISkin skin) - { - Debug.Assert(rulesetSkinProvider == null); - - if (Skin != null) - throw new InvalidOperationException("A skin has already been set."); - - Skin = skin; - } - - protected override RulesetSkinProvidingContainer CreateRulesetSkinProvider(Ruleset ruleset, IBeatmap beatmap, ISkin beatmapSkin) - => rulesetSkinProvider = new TestSkinProvidingContainer(Skin, ruleset, beatmap, beatmapSkin); - - private class TestSkinProvidingContainer : RulesetSkinProvidingContainer - { - private readonly ISkin skin; - - public TestSkinProvidingContainer(ISkin skin, Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) - : base(ruleset, beatmap, beatmapSkin) - { - this.skin = skin; - } - - protected override void UpdateSkins() - { - base.UpdateSkins(); - - if (skin != null) - SkinSources.Insert(0, skin is LegacySkin ? GetLegacyRulesetTransformedSkin(skin) : skin); - } - } } } From d0cdc07b1167fefd67bc1db900b9edf302104343 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 10:49:21 +0300 Subject: [PATCH 512/520] Reuse `AllSources` when looking up on `FindProvider` --- osu.Game/Skinning/SkinManager.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 6bd4888eb5..e220cad42a 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -236,14 +236,11 @@ namespace osu.Game.Skinning public ISkin FindProvider(Func lookupFunction) { - if (lookupFunction(CurrentSkin.Value)) - return CurrentSkin.Value; - - if (CurrentSkin.Value is LegacySkin && lookupFunction(DefaultLegacySkin)) - return DefaultLegacySkin; - - if (lookupFunction(DefaultSkin)) - return DefaultSkin; + foreach (var source in AllSources) + { + if (lookupFunction(source)) + return source; + } return null; } From c1284940e1da334498ca471952b2f8a4c8663033 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 10:49:37 +0300 Subject: [PATCH 513/520] Fix potentially providing the same skin instance twice in `AllSources` --- osu.Game/Skinning/SkinManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index e220cad42a..3234cca0ac 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -251,10 +251,11 @@ namespace osu.Game.Skinning { yield return CurrentSkin.Value; - if (CurrentSkin.Value is LegacySkin) + if (CurrentSkin.Value is LegacySkin && CurrentSkin.Value != DefaultLegacySkin) yield return DefaultLegacySkin; - yield return DefaultSkin; + if (CurrentSkin.Value != DefaultSkin) + yield return DefaultSkin; } } From c4fde635c6f44c18687475a0f76401d9d66a35e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 17:41:20 +0900 Subject: [PATCH 514/520] Ensure duplicate mods cannot be defined --- osu.Game.Tests/Mods/ModUtilsTest.cs | 8 ++++++++ osu.Game/Utils/ModUtils.cs | 23 ++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 9f27289d7e..0fac4521d9 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -14,6 +14,14 @@ namespace osu.Game.Tests.Mods [TestFixture] public class ModUtilsTest { + [Test] + public void TestModIsNotCompatibleWithItself() + { + var mod = new Mock(); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object, mod.Object }, out var invalid), Is.False); + Assert.That(invalid, Is.EquivalentTo(new[] { mod.Object })); + } + [Test] public void TestModIsCompatibleByItself() { diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 98766cb844..7485950f47 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -51,14 +51,31 @@ namespace osu.Game.Utils /// Whether all s in the combination are compatible with each-other. public static bool CheckCompatibleSet(IEnumerable combination, [NotNullWhen(false)] out List? invalidMods) { - combination = FlattenMods(combination).ToArray(); + var mods = FlattenMods(combination).ToArray(); invalidMods = null; - foreach (var mod in combination) + // ensure there are no duplicate mod definitions. + for (int i = 0; i < mods.Length; i++) + { + var candidate = mods[i]; + + for (int j = i + 1; j < mods.Length; j++) + { + var m = mods[j]; + + if (candidate.Equals(m)) + { + invalidMods ??= new List(); + invalidMods.Add(m); + } + } + } + + foreach (var mod in mods) { foreach (var type in mod.IncompatibleMods) { - foreach (var invalid in combination.Where(m => type.IsInstanceOfType(m))) + foreach (var invalid in mods.Where(m => type.IsInstanceOfType(m))) { if (invalid == mod) continue; From 6e0801b8529e5e3a832dd90ea56c1bc9e8638ee2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 17:41:27 +0900 Subject: [PATCH 515/520] Fix incorrect existing test case --- osu.Game.Tests/Mods/ModUtilsTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 0fac4521d9..4c126f0a3b 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -155,7 +155,7 @@ namespace osu.Game.Tests.Mods // multi mod. new object[] { - new Mod[] { new MultiMod(new OsuModHalfTime()), new OsuModHalfTime() }, + new Mod[] { new MultiMod(new OsuModHalfTime()), new OsuModDaycore() }, new[] { typeof(MultiMod) } }, // valid pair. From b8126e3ca8440180aaa1cc1812a0763f27e599e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 17:59:24 +0900 Subject: [PATCH 516/520] 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 1dc99bb60a..3c4380e355 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 3c52405f8e..f91620bd25 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 3689ce51f2..22c4340ba2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 31cbb36a647f6221506da9cce093a6f0ed850d4b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 12:02:21 +0300 Subject: [PATCH 517/520] Implement `FindProvider` and `AllSources` properly on all test `ISkinSource`s --- osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs | 4 ++-- osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs | 4 ++-- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 4 ++-- osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs | 5 +++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index fe962d3cb8..06bdb562e4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -114,9 +114,9 @@ namespace osu.Game.Rulesets.Osu.Tests public IBindable GetConfig(TLookup lookup) => null; - public ISkin FindProvider(Func lookupFunction) => null; + public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; - public IEnumerable AllSources => Enumerable.Empty(); + public IEnumerable AllSources => new[] { this }; public event Action SourceChanged { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 334d27e0a9..662cbaee68 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -167,9 +167,9 @@ namespace osu.Game.Rulesets.Osu.Tests public IBindable GetConfig(TLookup lookup) => null; - public ISkin FindProvider(Func lookupFunction) => null; + public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; - public IEnumerable AllSources => Enumerable.Empty(); + public IEnumerable AllSources => new[] { this }; public event Action SourceChanged; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 3317d8f80a..f29fbbf52b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -331,7 +331,7 @@ namespace osu.Game.Tests.Visual.Gameplay public ISkin FindProvider(Func lookupFunction) => throw new NotImplementedException(); - public IEnumerable AllSources => Enumerable.Empty(); + public IEnumerable AllSources => throw new NotImplementedException(); public event Action SourceChanged { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 59edb527eb..51f8b90c8f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -147,8 +147,8 @@ namespace osu.Game.Tests.Visual.Gameplay public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT); public ISample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => source?.GetConfig(lookup); - public ISkin FindProvider(Func lookupFunction) => source?.FindProvider(lookupFunction); - public IEnumerable AllSources => source.AllSources; + public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : source?.FindProvider(lookupFunction); + public IEnumerable AllSources => new[] { this }.Concat(source?.AllSources); public void TriggerSourceChanged() { diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs index 86c75c297a..e0c2965fa0 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -159,8 +159,9 @@ namespace osu.Game.Tests.Beatmaps remove { } } - public ISkin FindProvider(Func lookupFunction) => null; - public IEnumerable AllSources => Enumerable.Empty(); + public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; + + public IEnumerable AllSources => new[] { this }; } } } From ece63b9ba1ee6cc8683255dab0abf7d5cc439ee0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 12:03:55 +0300 Subject: [PATCH 518/520] Remove unused using directive --- osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index 06bdb562e4..211b0e8145 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; From 71e2815e7ec670693bf94ad8885e10b13015f338 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 12:05:17 +0300 Subject: [PATCH 519/520] Update and improve code documentation Co-authored-by: Dean Herbert --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 5 ++--- osu.Game/Skinning/SkinManager.cs | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index f1cc3df3de..c48aeca99a 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -13,8 +13,7 @@ namespace osu.Game.Skinning { /// /// A type of specialized for and other gameplay-related components. - /// Providing access to the skin sources and the beatmap skin each surrounded with the ruleset legacy skin transformer. - /// While also limiting lookups from falling back to any parent s out of this container. + /// Providing access to parent skin sources and the beatmap skin each surrounded with the ruleset legacy skin transformer. /// public class RulesetSkinProvidingContainer : SkinProvidingContainer { @@ -22,7 +21,7 @@ namespace osu.Game.Skinning protected readonly IBeatmap Beatmap; /// - /// This container already re-exposes all skin sources in a ruleset-usable form. + /// This container already re-exposes all parent sources in a ruleset-usable form. /// Therefore disallow falling back to any parent any further. /// protected override bool AllowFallingBackToParent => false; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 3234cca0ac..4cde4cd2b8 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -34,8 +34,8 @@ namespace osu.Game.Skinning /// Handles the storage and retrieval of s. /// /// - /// This is also exposed and cached as on a game-wide level for general components across the game. - /// Lookups from gameplay components are instead covered by , and are never hit here. + /// This is also exposed and cached as to allow for any component to potentially have skinning support. + /// For gameplay components, see which adds extra legacy and toggle logic that may affect the lookup process. /// [ExcludeFromDynamicCompile] public class SkinManager : ArchiveModelManager, ISkinSource, IStorageResourceProvider From 37f7486fb1f2be6c5c7a2cf355266ec811265f77 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 12:25:29 +0300 Subject: [PATCH 520/520] Fix potential null reference in LINQ method --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 51f8b90c8f..ccf13e1e8f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => source?.GetConfig(lookup); public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : source?.FindProvider(lookupFunction); - public IEnumerable AllSources => new[] { this }.Concat(source?.AllSources); + public IEnumerable AllSources => new[] { this }.Concat(source?.AllSources ?? Enumerable.Empty()); public void TriggerSourceChanged() {