From 6e812ebd566c1598303fb2e7ccf5a3cd13f4458e Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 16 Dec 2019 00:45:33 +0100 Subject: [PATCH 0001/1621] 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 0002/1621] 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 0003/1621] 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 0004/1621] 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 0005/1621] 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 0006/1621] 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 0007/1621] 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 0008/1621] 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 0009/1621] 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 0010/1621] 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 0011/1621] 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 0012/1621] 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 0013/1621] 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 0014/1621] 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 0015/1621] 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 7bb984eb8da4e0390ad9671330a0bb61d2a8920f Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 21 Dec 2019 20:47:34 +0800 Subject: [PATCH 0016/1621] Basic kiai flash implementation --- .../Objects/Drawables/Pieces/CirclePiece.cs | 4 ++ .../Objects/Drawables/Pieces/KiaiFlash.cs | 40 +++++++++++++++++++ .../Skinning/LegacyMainCirclePiece.cs | 12 ++++++ 3 files changed, 56 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index aab01f45d4..84470d7f30 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -35,6 +35,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Origin = Anchor.Centre, Texture = textures.Get(@"Gameplay/osu/disc"), }, + new KiaiFlash + { + RelativeSizeAxes = Axes.Both, + }, new TrianglesPiece { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs new file mode 100644 index 0000000000..9f9ca42d69 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class KiaiFlash : BeatSyncedContainer + { + public Drawable FlashComponent { get; set; } + + public KiaiFlash() + { + Blending = BlendingParameters.Additive; + + Child = FlashComponent = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(1f), + Alpha = 0f, + }; + } + + protected new double EarlyActivationMilliseconds = 80; + + protected override void OnNewBeat(int beatIndex, Game.Beatmaps.ControlPoints.TimingControlPoint timingPoint, Game.Beatmaps.ControlPoints.EffectControlPoint effectPoint, Framework.Audio.Track.TrackAmplitudes amplitudes) + { + if (effectPoint.KiaiMode) + { + FlashComponent + .FadeTo(0.25f, EarlyActivationMilliseconds, Easing.OutQuint) + .Then() + .FadeOut(timingPoint.BeatLength - 80, Easing.OutSine); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 93ae0371df..97541d171b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -10,6 +10,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -44,6 +45,17 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, }, + new KiaiFlash + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.921875f), + + Masking = true, + CornerRadius = Size.X / 2, + CornerExponent = 2, + }, hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40), From e4eccb86ba62985bbae398a5b5697269f01df72f Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 21 Dec 2019 20:53:02 +0800 Subject: [PATCH 0017/1621] Add property Intensity --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs | 1 + osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs | 4 +++- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index 84470d7f30..e6b193b00f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces new KiaiFlash { RelativeSizeAxes = Axes.Both, + Intensity = 0.25f, }, new TrianglesPiece { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs index 9f9ca42d69..e7c880c3c1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs @@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public Drawable FlashComponent { get; set; } + public float Intensity { get; set; } + public KiaiFlash() { Blending = BlendingParameters.Additive; @@ -31,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (effectPoint.KiaiMode) { FlashComponent - .FadeTo(0.25f, EarlyActivationMilliseconds, Easing.OutQuint) + .FadeTo(Intensity, EarlyActivationMilliseconds, Easing.OutQuint) .Then() .FadeOut(timingPoint.BeatLength - 80, Easing.OutSine); } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 97541d171b..ce89fac345 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -55,6 +55,8 @@ namespace osu.Game.Rulesets.Osu.Skinning Masking = true, CornerRadius = Size.X / 2, CornerExponent = 2, + + Intensity = 0.1f }, hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { From 76a895f34830a8389920cedb1d7db47458c7c2df Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 21 Dec 2019 22:44:52 +0800 Subject: [PATCH 0018/1621] improve code quality --- .../Objects/Drawables/Pieces/CirclePiece.cs | 8 ++++++++ .../Objects/Drawables/Pieces/KiaiFlash.cs | 14 ++------------ .../Skinning/LegacyMainCirclePiece.cs | 11 +++++++++-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index e6b193b00f..eba3a02376 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -6,7 +6,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Shapes; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -39,6 +41,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { RelativeSizeAxes = Axes.Both, Intensity = 0.25f, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Alpha = 0f, + }, }, new TrianglesPiece { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs index e7c880c3c1..379deff8bf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs @@ -10,29 +10,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class KiaiFlash : BeatSyncedContainer { - public Drawable FlashComponent { get; set; } - public float Intensity { get; set; } public KiaiFlash() { + EarlyActivationMilliseconds = 80; Blending = BlendingParameters.Additive; - - Child = FlashComponent = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(1f), - Alpha = 0f, - }; } - protected new double EarlyActivationMilliseconds = 80; - protected override void OnNewBeat(int beatIndex, Game.Beatmaps.ControlPoints.TimingControlPoint timingPoint, Game.Beatmaps.ControlPoints.EffectControlPoint effectPoint, Framework.Audio.Track.TrackAmplitudes amplitudes) { if (effectPoint.KiaiMode) { - FlashComponent + Child .FadeTo(Intensity, EarlyActivationMilliseconds, Easing.OutQuint) .Then() .FadeOut(timingPoint.BeatLength - 80, Easing.OutSine); diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index ce89fac345..27f9e008ed 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; @@ -50,13 +51,19 @@ namespace osu.Game.Rulesets.Osu.Skinning RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(0.921875f), + Size = new Vector2(OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / 64), Masking = true, CornerRadius = Size.X / 2, CornerExponent = 2, - Intensity = 0.1f + Intensity = 0.1f, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Alpha = 0f, + } }, hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { From 4b3cfe3baec4d44d7a5f04d5398199ca1d95eb00 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 22 Dec 2019 20:01:58 +0800 Subject: [PATCH 0019/1621] temporarily remove kiai flash for legacy circles --- .../Objects/Drawables/Pieces/CirclePiece.cs | 10 +------- .../Objects/Drawables/Pieces/KiaiFlash.cs | 23 +++++++++++++------ .../Skinning/LegacyMainCirclePiece.cs | 21 ----------------- 3 files changed, 17 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index eba3a02376..8d68714f9a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -6,9 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Shapes; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -40,13 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces new KiaiFlash { RelativeSizeAxes = Axes.Both, - Intensity = 0.25f, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - Alpha = 0f, - }, + FlashOpacity = 0.25f, }, new TrianglesPiece { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs index 379deff8bf..62ea028b11 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs @@ -3,30 +3,39 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class KiaiFlash : BeatSyncedContainer { - public float Intensity { get; set; } + public float FlashOpacity = 1f; public KiaiFlash() { EarlyActivationMilliseconds = 80; Blending = BlendingParameters.Additive; + + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Alpha = 0f, + }; } protected override void OnNewBeat(int beatIndex, Game.Beatmaps.ControlPoints.TimingControlPoint timingPoint, Game.Beatmaps.ControlPoints.EffectControlPoint effectPoint, Framework.Audio.Track.TrackAmplitudes amplitudes) { - if (effectPoint.KiaiMode) + if (!effectPoint.KiaiMode) { - Child - .FadeTo(Intensity, EarlyActivationMilliseconds, Easing.OutQuint) - .Then() - .FadeOut(timingPoint.BeatLength - 80, Easing.OutSine); + return; } + + Child + .FadeTo(FlashOpacity, EarlyActivationMilliseconds, Easing.OutQuint) + .Then() + .FadeOut(timingPoint.BeatLength - 80, Easing.OutSine); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 27f9e008ed..93ae0371df 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -6,12 +6,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -46,25 +44,6 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - new KiaiFlash - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / 64), - - Masking = true, - CornerRadius = Size.X / 2, - CornerExponent = 2, - - Intensity = 0.1f, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - Alpha = 0f, - } - }, hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40), From 0d812bce9f90a8a77cd413386e9a6c4cdea90c44 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 26 Dec 2019 03:32:40 +0100 Subject: [PATCH 0020/1621] 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 0021/1621] 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 0022/1621] 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 0023/1621] 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 0024/1621] 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 0025/1621] 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 0026/1621] 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 0027/1621] 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 0028/1621] 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 0029/1621] 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 0030/1621] 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 0031/1621] 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 0032/1621] 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 0033/1621] 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 0034/1621] 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 0035/1621] 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 0036/1621] 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 0037/1621] 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 0038/1621] 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 0039/1621] 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 0040/1621] 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 0041/1621] 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 0042/1621] 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 0043/1621] 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 0044/1621] 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 0045/1621] 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 0046/1621] 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 0047/1621] 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 0048/1621] 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 0049/1621] 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 0050/1621] 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 0051/1621] 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 0052/1621] 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 0053/1621] 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 0054/1621] 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 0055/1621] 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 0056/1621] 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 0057/1621] 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 0058/1621] 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 0059/1621] 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 0060/1621] 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 0061/1621] 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 0062/1621] 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 0063/1621] 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 0064/1621] 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 0065/1621] 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 0066/1621] 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 c5995acfff37684fabc9b53e3c005eb5f5184faa Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 30 Jan 2020 12:30:25 +0800 Subject: [PATCH 0067/1621] linkify metadata --- osu.Game/Online/Chat/MessageFormatter.cs | 3 +- osu.Game/OsuGame.cs | 10 ++++++ osu.Game/Overlays/BeatmapSet/Info.cs | 43 +++++++++++++++++------ osu.Game/Overlays/DirectOverlay.cs | 6 ++++ osu.Game/Screens/Select/BeatmapDetails.cs | 42 +++++++++++++++++----- 5 files changed, 85 insertions(+), 19 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 717de18c14..cfd0dcca50 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -279,7 +279,8 @@ namespace osu.Game.Online.Chat JoinMultiplayerMatch, Spectate, OpenUserProfile, - Custom + Custom, + OpenDirectWithSearch } public class Link : IComparable diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ff46c6d849..78e033c972 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -244,6 +244,10 @@ namespace osu.Game ShowChannel(link.Argument); break; + case LinkAction.OpenDirectWithSearch: + ShowDirectWithSearch(link.Argument); + break; + case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: @@ -310,6 +314,12 @@ namespace osu.Game /// The beatmap to show. public void ShowBeatmap(int beatmapId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId)); + /// + /// Show Direct with a query. + /// + /// The query to search for.null + public void ShowDirectWithSearch(string query) => waitForReady(() => direct, _ => direct.ShowWithSearch(query)); + /// /// Present a beatmap at song select immediately. /// The user should have already requested this interactively. diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 16d6236051..b621cf1cce 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -8,10 +8,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; +using osu.Game.Screens.Select; using osuTK; using osuTK.Graphics; @@ -68,7 +71,7 @@ namespace osu.Game.Overlays.BeatmapSet Child = new Container { RelativeSizeAxes = Axes.Both, - Child = new MetadataSection("Description"), + Child = new MetadataSection(MetadataType.Description), }, }, new Container @@ -86,10 +89,10 @@ namespace osu.Game.Overlays.BeatmapSet Direction = FillDirection.Full, Children = new[] { - source = new MetadataSection("Source"), - genre = new MetadataSection("Genre") { Width = 0.5f }, - language = new MetadataSection("Language") { Width = 0.5f }, - tags = new MetadataSection("Tags"), + source = new MetadataSection(MetadataType.Source), + genre = new MetadataSection(MetadataType.Genre) { Width = 0.5f }, + language = new MetadataSection(MetadataType.Language) { Width = 0.5f }, + tags = new MetadataSection(MetadataType.Tags), }, }, }, @@ -133,8 +136,9 @@ namespace osu.Game.Overlays.BeatmapSet private class MetadataSection : FillFlowContainer { + private readonly MetadataType type; private readonly OsuSpriteText header; - private readonly TextFlowContainer textFlow; + private readonly LinkFlowContainer textFlow; public string Text { @@ -147,13 +151,32 @@ namespace osu.Game.Overlays.BeatmapSet } this.FadeIn(transition_duration); + textFlow.Clear(); - textFlow.AddText(value, s => s.Font = s.Font.With(size: 14)); + static void format(SpriteText t) => t.Font = t.Font.With(size: 14); + + switch(type) + { + case MetadataType.Tags: + foreach (string tag in value.Split(" ")) + textFlow.AddLink(tag + " ", LinkAction.OpenDirectWithSearch, tag, null, format); + break; + + case MetadataType.Source: + textFlow.AddLink(value, LinkAction.OpenDirectWithSearch, value, null, format); + break; + + default: + textFlow.AddText(value, format); + break; + } } } - public MetadataSection(string title) + public MetadataSection(MetadataType type) { + this.type = type; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Spacing = new Vector2(5f); @@ -162,12 +185,12 @@ namespace osu.Game.Overlays.BeatmapSet { header = new OsuSpriteText { - Text = title, + Text = this.type.ToString(), Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), Shadow = false, Margin = new MarginPadding { Top = 20 }, }, - textFlow = new OsuTextFlowContainer + textFlow = new LinkFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index e4cef319fe..c8e72cc6c9 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -154,6 +154,12 @@ namespace osu.Game.Overlays updateResultCounts(); } + public void ShowWithSearch(string query) + { + currentQuery.Value = query; + Show(); + } + [BackgroundDependencyLoader] private void load(OsuColour colours, RulesetStore rulesets, PreviewTrackManager previewTrackManager) { diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 577d999388..07a18ac5a3 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -19,6 +19,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; +using osu.Game.Overlays; +using osu.Game.Online.Chat; namespace osu.Game.Screens.Select { @@ -127,9 +129,9 @@ namespace osu.Game.Screens.Select Margin = new MarginPadding { Top = spacing * 2 }, Children = new[] { - description = new MetadataSection("Description"), - source = new MetadataSection("Source"), - tags = new MetadataSection("Tags"), + description = new MetadataSection(MetadataType.Description), + source = new MetadataSection(MetadataType.Source), + tags = new MetadataSection(MetadataType.Tags), }, }, }, @@ -299,10 +301,11 @@ namespace osu.Game.Screens.Select private class MetadataSection : Container { private readonly FillFlowContainer textContainer; + private readonly MetadataType type; private TextFlowContainer textFlow; - - public MetadataSection(string title) + public MetadataSection(MetadataType type) { + this.type = type; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -320,7 +323,7 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Y, Child = new OsuSpriteText { - Text = title, + Text = this.type.ToString(), Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), }, }, @@ -346,15 +349,29 @@ namespace osu.Game.Screens.Select private void setTextAsync(string text) { - LoadComponentAsync(new OsuTextFlowContainer(s => s.Font = s.Font.With(size: 14)) + LoadComponentAsync(new LinkFlowContainer(s => s.Font = s.Font.With(size: 14)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Colour = Color4.White.Opacity(0.75f), - Text = text }, loaded => { textFlow?.Expire(); + + switch(type) + { + case MetadataType.Tags: + foreach (string tag in text.Split(" ")) + loaded.AddLink(tag + " ", LinkAction.OpenDirectWithSearch, tag); + break; + case MetadataType.Source: + loaded.AddLink(text, LinkAction.OpenDirectWithSearch, text); + break; + default: + loaded.AddText(text); + break; + } + textContainer.Add(textFlow = loaded); // fade in if we haven't yet. @@ -363,4 +380,13 @@ namespace osu.Game.Screens.Select } } } + + public enum MetadataType + { + Tags, + Source, + Description, + Genre, + Language + } } From 2274d70dacbea11dbf6012eefa69cbdbeb88f860 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 31 Jan 2020 06:41:50 +0800 Subject: [PATCH 0068/1621] apply suggestions --- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- osu.Game/OsuGame.cs | 8 ++++---- osu.Game/Overlays/BeatmapSet/Info.cs | 12 +++++++++--- osu.Game/Screens/Select/BeatmapDetails.cs | 12 +++++++++--- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index cfd0dcca50..28cb57227d 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -279,8 +279,8 @@ namespace osu.Game.Online.Chat JoinMultiplayerMatch, Spectate, OpenUserProfile, + SearchBeatmapSet, Custom, - OpenDirectWithSearch } public class Link : IComparable diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 78e033c972..1aa4017665 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -244,8 +244,8 @@ namespace osu.Game ShowChannel(link.Argument); break; - case LinkAction.OpenDirectWithSearch: - ShowDirectWithSearch(link.Argument); + case LinkAction.SearchBeatmapSet: + SearchBeatmapSet(link.Argument); break; case LinkAction.OpenEditorTimestamp: @@ -317,8 +317,8 @@ namespace osu.Game /// /// Show Direct with a query. /// - /// The query to search for.null - public void ShowDirectWithSearch(string query) => waitForReady(() => direct, _ => direct.ShowWithSearch(query)); + /// The query to search for. + public void SearchBeatmapSet(string query) => waitForReady(() => direct, _ => direct.ShowWithSearch(query)); /// /// Present a beatmap at song select immediately. diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index b621cf1cce..c566cfa859 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -158,12 +158,18 @@ namespace osu.Game.Overlays.BeatmapSet switch(type) { case MetadataType.Tags: - foreach (string tag in value.Split(" ")) - textFlow.AddLink(tag + " ", LinkAction.OpenDirectWithSearch, tag, null, format); + string[] tags = value.Split(" "); + for (int i = 0; i <= tags.Length - 1; i++) + { + textFlow.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i], null, format); + + if (i != tags.Length - 1) + textFlow.AddText(" ", format); + } break; case MetadataType.Source: - textFlow.AddLink(value, LinkAction.OpenDirectWithSearch, value, null, format); + textFlow.AddLink(value, LinkAction.SearchBeatmapSet, value, null, format); break; default: diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 07a18ac5a3..42e4c22ff1 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -361,11 +361,17 @@ namespace osu.Game.Screens.Select switch(type) { case MetadataType.Tags: - foreach (string tag in text.Split(" ")) - loaded.AddLink(tag + " ", LinkAction.OpenDirectWithSearch, tag); + string[] tags = text.Split(" "); + for (int i = 0; i <= tags.Length - 1; i++) + { + loaded.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i]); + + if (i != tags.Length - 1) + loaded.AddText(" "); + } break; case MetadataType.Source: - loaded.AddLink(text, LinkAction.OpenDirectWithSearch, text); + loaded.AddLink(text, LinkAction.SearchBeatmapSet, text); break; default: loaded.AddText(text); From b916366536b3bd0b4f13a377237aaa6788d1d0c2 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 31 Jan 2020 08:17:39 +0800 Subject: [PATCH 0069/1621] fix formatting --- osu.Game/Overlays/BeatmapSet/Info.cs | 2 +- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index c566cfa859..8be9462ffc 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -155,7 +155,7 @@ namespace osu.Game.Overlays.BeatmapSet textFlow.Clear(); static void format(SpriteText t) => t.Font = t.Font.With(size: 14); - switch(type) + switch (type) { case MetadataType.Tags: string[] tags = value.Split(" "); diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 42e4c22ff1..cd5d2e8e45 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -358,7 +358,7 @@ namespace osu.Game.Screens.Select { textFlow?.Expire(); - switch(type) + switch (type) { case MetadataType.Tags: string[] tags = text.Split(" "); From 2952fc8cc4ec21b146c1d2f6187608284bd28cae Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 31 Jan 2020 09:05:13 +0800 Subject: [PATCH 0070/1621] remove unused using --- osu.Game/Screens/Select/BeatmapDetails.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index cd5d2e8e45..59f97d696e 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; -using osu.Game.Overlays; using osu.Game.Online.Chat; namespace osu.Game.Screens.Select From 79f47fe7d791b57872158456a264cab4782ae35e Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 3 Feb 2020 22:08:01 +0100 Subject: [PATCH 0071/1621] 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 0072/1621] 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 0073/1621] 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 0074/1621] 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 0075/1621] 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 0076/1621] 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 0077/1621] 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 0078/1621] 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 0079/1621] 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 0080/1621] 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 0081/1621] 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 0082/1621] 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 0083/1621] 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 62e676feb5e6ca8a4d352f96c51ffd4cc8c01446 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:18:02 +0100 Subject: [PATCH 0084/1621] Restyle SpotlightsDropdown to match osu-web --- .../Overlays/Rankings/SpotlightSelector.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index f019b50ae8..13640c7fe7 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -165,8 +165,28 @@ namespace osu.Game.Overlays.Rankings [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { + // osu-web adds a 0.6 opacity container on top of the 0.5 base one when hovering, 0.8 on a single container here matches the resulting colour + AccentColour = colourProvider.Background6.Opacity(0.8f); menu.BackgroundColour = colourProvider.Background5; - AccentColour = colourProvider.Background6; + Padding = new MarginPadding { Vertical = 20 }; + } + + private class SpotlightsDropdownHeader : OsuDropdownHeader + { + public SpotlightsDropdownHeader() : base() + { + Height = 48; + Text.Font = OsuFont.GetFont(size: 15); + Foreground.Padding = new MarginPadding { Horizontal = 10, Vertical = 15 }; + Margin = Icon.Margin = new MarginPadding(0); + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + BackgroundColour = colourProvider.Background6.Opacity(0.5f); + BackgroundColourHover = colourProvider.Background5; + } } } } From 82cbd35e30940df06e6197bda5dbfba12eb47a62 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:22:22 +0100 Subject: [PATCH 0085/1621] Make CountryName use LinkFlowContainer for consistency --- .../Rankings/Tables/CountriesTable.cs | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 0b9a48ce0e..5306000d6a 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -5,11 +5,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System; using osu.Game.Users; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Rankings.Tables { @@ -62,35 +61,22 @@ namespace osu.Game.Overlays.Rankings.Tables } }; - private class CountryName : OsuHoverContainer + private class CountryName : LinkFlowContainer { - protected override IEnumerable EffectTargets => new[] { text }; - [Resolved(canBeNull: true)] private RankingsOverlay rankings { get; set; } - private readonly OsuSpriteText text; private readonly Country country; - public CountryName(Country country) + public CountryName(Country country) : base(t => t.Font = OsuFont.GetFont(size: 12)) { this.country = country; - AutoSizeAxes = Axes.Both; - Add(text = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12), - Text = country.FullName ?? string.Empty, - }); - } + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + TextAnchor = Anchor.CentreLeft; - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - IdleColour = colourProvider.Light2; - HoverColour = colourProvider.Content2; - - Action = () => rankings?.ShowCountry(country); + AddLink(country.FullName ?? string.Empty, () => rankings?.ShowCountry(country)); } } } From f03ada65ddec5c5b38f125bf49b776a0c80a5f0a Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:23:50 +0100 Subject: [PATCH 0086/1621] Adjust grade columns spacing --- .../Overlays/Rankings/Tables/RankingsTable.cs | 10 +++++++--- .../Overlays/Rankings/Tables/UserBasedTable.cs | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 943897581e..946bd82cf8 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -96,19 +96,23 @@ namespace osu.Game.Overlays.Rankings.Tables } }; + protected virtual IEnumerable GradeColumns() => new List(); + protected virtual string HighlightedColumn() => @"Performance"; private class HeaderText : OsuSpriteText { private readonly string highlighted; - public HeaderText(string text, string highlighted) + public HeaderText(string text, string highlighted, IEnumerable gradeColumns) { this.highlighted = highlighted; Text = text; Font = OsuFont.GetFont(size: 12); - Margin = new MarginPadding { Horizontal = 10 }; + + var isGrade = gradeColumns.Contains(text); + Margin = new MarginPadding { Vertical = 5, Horizontal = isGrade ? 20 : 10 }; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index cad7364103..8eecffd738 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -19,16 +19,18 @@ namespace osu.Game.Overlays.Rankings.Tables { } - protected override TableColumn[] CreateAdditionalHeaders() => new[] + protected override IEnumerable GradeColumns() => new List() { "SS", "S", "A" }; + + protected override TableColumn[] CreateAdditionalHeaders() { + var gradeColumns = GradeColumns().Select(grade => new TableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize))); + + return new[] + { new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - }.Concat(CreateUniqueHeaders()).Concat(new[] - { - new TableColumn("SS", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("S", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - }).ToArray(); + }.Concat(CreateUniqueHeaders()).Concat(gradeColumns).ToArray(); + } protected sealed override Country GetCountry(UserStatistics item) => item.User.Country; From 9e4a6a9cf3cf309f7c88e4791e438b605d3116f5 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:25:49 +0100 Subject: [PATCH 0087/1621] Add spacing between RankingsTable rows to match osu-web --- .../Overlays/Rankings/Tables/RankingsTable.cs | 23 +++++++++++-------- .../Rankings/Tables/TableRowBackground.cs | 7 +++--- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 946bd82cf8..e7da5c01fb 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -20,7 +20,8 @@ namespace osu.Game.Overlays.Rankings.Tables { protected const int TEXT_SIZE = 12; private const float horizontal_inset = 20; - private const float row_height = 25; + private const float row_height = 32; + private const float row_spacing = 3; private const int items_per_page = 50; private readonly int page; @@ -35,7 +36,7 @@ namespace osu.Game.Overlays.Rankings.Tables AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Horizontal = horizontal_inset }; - RowSize = new Dimension(GridSizeMode.Absolute, row_height); + RowSize = new Dimension(GridSizeMode.Absolute, row_height + row_spacing); } [BackgroundDependencyLoader] @@ -47,10 +48,11 @@ namespace osu.Game.Overlays.Rankings.Tables { RelativeSizeAxes = Axes.Both, Depth = 1f, - Margin = new MarginPadding { Top = row_height } + Margin = new MarginPadding { Top = row_height + row_spacing }, + Spacing = new Vector2(0, row_spacing), }); - rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground())); + rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground(row_height))); Columns = mainHeaders.Concat(CreateAdditionalHeaders()).ToArray(); Content = rankings.Select((s, i) => createContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); @@ -68,13 +70,13 @@ namespace osu.Game.Overlays.Rankings.Tables protected abstract Drawable[] CreateAdditionalContent(TModel item); - protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty, HighlightedColumn()); + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty, HighlightedColumn(), GradeColumns()); protected abstract Country GetCountry(TModel item); protected abstract Drawable CreateFlagContent(TModel item); - private OsuSpriteText createIndexDrawable(int index) => new OsuSpriteText + private OsuSpriteText createIndexDrawable(int index) => new RowText { Text = $"#{index + 1}", Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.SemiBold) @@ -84,12 +86,13 @@ namespace osu.Game.Overlays.Rankings.Tables { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(7, 0), + Spacing = new Vector2(10, 0), + Margin = new MarginPadding { Bottom = row_spacing }, Children = new[] { new UpdateableFlag(GetCountry(item)) { - Size = new Vector2(20, 13), + Size = new Vector2(30, 20), ShowPlaceholderOnNull = false, }, CreateFlagContent(item) @@ -128,7 +131,7 @@ namespace osu.Game.Overlays.Rankings.Tables public RowText() { Font = OsuFont.GetFont(size: TEXT_SIZE); - Margin = new MarginPadding { Horizontal = 10 }; + Margin = new MarginPadding { Horizontal = 10, Bottom = row_spacing }; } } diff --git a/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs b/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs index fe87a8b3d4..d5e2f12172 100644 --- a/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs +++ b/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs @@ -19,13 +19,14 @@ namespace osu.Game.Overlays.Rankings.Tables private Color4 idleColour; private Color4 hoverColour; - public TableRowBackground() + public TableRowBackground(float height) { RelativeSizeAxes = Axes.X; - Height = 25; + Height = height; - CornerRadius = 3; + CornerRadius = 4; Masking = true; + MaskingSmoothness = 0.5f; InternalChild = background = new Box { From 1d6746102f682469d9528a851d8d804e1f2b3e09 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:26:21 +0100 Subject: [PATCH 0088/1621] Adjust user links --- osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index 8eecffd738..45a2c20000 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -27,8 +27,8 @@ namespace osu.Game.Overlays.Rankings.Tables return new[] { - new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }.Concat(CreateUniqueHeaders()).Concat(gradeColumns).ToArray(); } @@ -36,7 +36,12 @@ namespace osu.Game.Overlays.Rankings.Tables protected sealed override Drawable CreateFlagContent(UserStatistics item) { - var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE, italics: true)) { AutoSizeAxes = Axes.Both }; + var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE, italics: true)) + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + TextAnchor = Anchor.CentreLeft + }; username.AddUserLink(item.User); return username; } From b65f5031a2e1a9ddc9c73ee0acea921a65cb7461 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:26:45 +0100 Subject: [PATCH 0089/1621] Adjust spacing in SpotlightSelector --- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 13640c7fe7..21e9881327 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics.UserInterface; using osu.Game.Online.API.Requests; +using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Overlays.Rankings { @@ -50,7 +51,7 @@ namespace osu.Game.Overlays.Rankings public SpotlightSelector() { RelativeSizeAxes = Axes.X; - Height = 100; + Height = 155; Add(content = new Container { RelativeSizeAxes = Axes.Both, @@ -63,7 +64,7 @@ namespace osu.Game.Overlays.Rankings new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN }, Children = new Drawable[] { dropdown = new SpotlightsDropdown @@ -128,6 +129,7 @@ namespace osu.Game.Overlays.Rankings { AutoSizeAxes = Axes.Both; Direction = FillDirection.Vertical; + Padding = new MarginPadding { Vertical = 15 }; Children = new Drawable[] { new OsuSpriteText @@ -138,12 +140,12 @@ namespace osu.Game.Overlays.Rankings new Container { AutoSizeAxes = Axes.X, - Height = 20, + Height = 25, Child = valueText = new OsuSpriteText { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 18, weight: FontWeight.Light), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light), } } }; From 92da7132cd616a6c3d7dbd5901f6fdfa351748be Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 27 Feb 2020 22:35:02 +0100 Subject: [PATCH 0090/1621] Fix code quality issues --- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 2 +- osu.Game/Overlays/Rankings/Tables/CountriesTable.cs | 7 ++----- osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 21e9881327..37e2c1c38b 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -175,7 +175,7 @@ namespace osu.Game.Overlays.Rankings private class SpotlightsDropdownHeader : OsuDropdownHeader { - public SpotlightsDropdownHeader() : base() + public SpotlightsDropdownHeader() { Height = 48; Text.Font = OsuFont.GetFont(size: 15); diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 5306000d6a..c43ecf9be5 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -66,12 +66,9 @@ namespace osu.Game.Overlays.Rankings.Tables [Resolved(canBeNull: true)] private RankingsOverlay rankings { get; set; } - private readonly Country country; - - public CountryName(Country country) : base(t => t.Font = OsuFont.GetFont(size: 12)) + public CountryName(Country country) + : base(t => t.Font = OsuFont.GetFont(size: 12)) { - this.country = country; - AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; TextAnchor = Anchor.CentreLeft; diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index 45a2c20000..8ce31c8539 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Rankings.Tables { } - protected override IEnumerable GradeColumns() => new List() { "SS", "S", "A" }; + protected override IEnumerable GradeColumns() => new List { "SS", "S", "A" }; protected override TableColumn[] CreateAdditionalHeaders() { From 6d09f1eea41d3cb3a01d19dcfc0e8f5daa2f2967 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Fri, 28 Feb 2020 13:18:40 +0100 Subject: [PATCH 0091/1621] Hook up SpotlightsDropdownHeader --- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 37e2c1c38b..408070f4c7 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -164,6 +164,8 @@ namespace osu.Game.Overlays.Rankings protected override DropdownMenu CreateMenu() => menu = base.CreateMenu().With(m => m.MaxHeight = 400); + protected override DropdownHeader CreateHeader() => new SpotlightsDropdownHeader(); + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { From 0760ccf0245afcdf60a87512dcec454a1dd85414 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Sat, 29 Feb 2020 15:14:25 +0100 Subject: [PATCH 0092/1621] Adjust CountryFilter spacing to match web --- osu.Game/Overlays/Rankings/CountryFilter.cs | 4 ++-- osu.Game/Overlays/Rankings/CountryPill.cs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Rankings/CountryFilter.cs b/osu.Game/Overlays/Rankings/CountryFilter.cs index 4bdefb06ef..9950f36141 100644 --- a/osu.Game/Overlays/Rankings/CountryFilter.cs +++ b/osu.Game/Overlays/Rankings/CountryFilter.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Rankings public class CountryFilter : CompositeDrawable, IHasCurrentValue { private const int duration = 200; - private const int height = 50; + private const int height = 70; private readonly BindableWithCurrent current = new BindableWithCurrent(); diff --git a/osu.Game/Overlays/Rankings/CountryPill.cs b/osu.Game/Overlays/Rankings/CountryPill.cs index 1b19bbd95e..edd7b596d2 100644 --- a/osu.Game/Overlays/Rankings/CountryPill.cs +++ b/osu.Game/Overlays/Rankings/CountryPill.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Rankings InternalChild = content = new CircularContainer { - Height = 25, + Height = 30, AutoSizeDuration = duration, AutoSizeEasing = Easing.OutQuint, Masking = true, @@ -58,9 +58,9 @@ namespace osu.Game.Overlays.Rankings Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, - Margin = new MarginPadding { Horizontal = 10 }, + Margin = new MarginPadding { Horizontal = 15 }, Direction = FillDirection.Horizontal, - Spacing = new Vector2(8, 0), + Spacing = new Vector2(15, 0), Children = new Drawable[] { new FillFlowContainer @@ -70,14 +70,14 @@ namespace osu.Game.Overlays.Rankings Anchor = Anchor.Centre, Origin = Anchor.Centre, Direction = FillDirection.Horizontal, - Spacing = new Vector2(3, 0), + Spacing = new Vector2(5, 0), Children = new Drawable[] { flag = new UpdateableFlag { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(22, 15) + Size = new Vector2(30, 20) }, countryName = new OsuSpriteText { @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Rankings AutoSizeAxes = Axes.Both; Add(icon = new SpriteIcon { - Size = new Vector2(8), + Size = new Vector2(10), Icon = FontAwesome.Solid.Times }); } From 520bbcf2e4d4fa56090b3da2b3680a34fab5a0d8 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Sat, 29 Feb 2020 15:19:36 +0100 Subject: [PATCH 0093/1621] Use correct x-axis margins --- osu.Game/Overlays/Rankings/CountryFilter.cs | 4 ++-- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Rankings/CountryFilter.cs b/osu.Game/Overlays/Rankings/CountryFilter.cs index 9950f36141..ab5df80d8a 100644 --- a/osu.Game/Overlays/Rankings/CountryFilter.cs +++ b/osu.Game/Overlays/Rankings/CountryFilter.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Rankings Origin = Anchor.CentreLeft, Direction = FillDirection.Horizontal, Spacing = new Vector2(10, 0), - Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Margin = new MarginPadding { Left = OverlayHeader.CONTENT_X_MARGIN }, Children = new Drawable[] { new OsuSpriteText diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 408070f4c7..4fb0d58e57 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Rankings new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN }, + Padding = new MarginPadding { Horizontal = OverlayHeader.CONTENT_X_MARGIN }, Children = new Drawable[] { dropdown = new SpotlightsDropdown From e09fbcb05fac1b881593810b1bed91d1ba69f101 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Wed, 4 Mar 2020 17:47:48 +0100 Subject: [PATCH 0094/1621] Only add link if the country name isn't null --- osu.Game/Overlays/Rankings/Tables/CountriesTable.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index c43ecf9be5..011a8207a0 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -73,7 +73,8 @@ namespace osu.Game.Overlays.Rankings.Tables RelativeSizeAxes = Axes.Y; TextAnchor = Anchor.CentreLeft; - AddLink(country.FullName ?? string.Empty, () => rankings?.ShowCountry(country)); + if (country.FullName != null) + AddLink(country.FullName, () => rankings?.ShowCountry(country)); } } } From 276a90fb8495f71b29e9886b7796746d754e4007 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Wed, 4 Mar 2020 18:19:28 +0100 Subject: [PATCH 0095/1621] Use public property instead of ctor parameter --- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 4 ++-- osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index e7da5c01fb..c03a279afa 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Rankings.Tables Spacing = new Vector2(0, row_spacing), }); - rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground(row_height))); + rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground { Height = row_height })); Columns = mainHeaders.Concat(CreateAdditionalHeaders()).ToArray(); Content = rankings.Select((s, i) => createContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); diff --git a/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs b/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs index d5e2f12172..b49fec65db 100644 --- a/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs +++ b/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -19,10 +19,9 @@ namespace osu.Game.Overlays.Rankings.Tables private Color4 idleColour; private Color4 hoverColour; - public TableRowBackground(float height) + public TableRowBackground() { RelativeSizeAxes = Axes.X; - Height = height; CornerRadius = 4; Masking = true; From 06642dc7ff26a99386b8a2c8d7d8a861cdc03bfc Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Wed, 4 Mar 2020 18:20:55 +0100 Subject: [PATCH 0096/1621] Improve HeaderText highlight and spacing logic --- .../Overlays/Rankings/Tables/RankingsTable.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index c03a279afa..eb2b676500 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -70,7 +70,14 @@ namespace osu.Game.Overlays.Rankings.Tables protected abstract Drawable[] CreateAdditionalContent(TModel item); - protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty, HighlightedColumn(), GradeColumns()); + protected override Drawable CreateHeader(int index, TableColumn column) + { + var title = column?.Header ?? string.Empty; + var isHighlighted = HighlightedColumn() == title; + var isGrade = GradeColumns().Contains(title); + + return new HeaderText(title, isHighlighted) { Margin = new MarginPadding { Vertical = 5, Horizontal = isGrade ? 20 : 10 } }; + } protected abstract Country GetCountry(TModel item); @@ -105,23 +112,20 @@ namespace osu.Game.Overlays.Rankings.Tables private class HeaderText : OsuSpriteText { - private readonly string highlighted; + private readonly bool isHighlighted; - public HeaderText(string text, string highlighted, IEnumerable gradeColumns) + public HeaderText(string text, bool isHighlighted) { - this.highlighted = highlighted; + this.isHighlighted = isHighlighted; Text = text; Font = OsuFont.GetFont(size: 12); - - var isGrade = gradeColumns.Contains(text); - Margin = new MarginPadding { Vertical = 5, Horizontal = isGrade ? 20 : 10 }; } [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - if (Text != highlighted) + if (isHighlighted) Colour = colourProvider.Foreground1; } } From 56f76580fdaa02a04575f4a7e1be4b5b231194fb Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Wed, 4 Mar 2020 18:22:56 +0100 Subject: [PATCH 0097/1621] Use Y-axis autosizing in SpotlightsDropdownHeader --- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 4fb0d58e57..d2cb94de9a 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -179,8 +179,9 @@ namespace osu.Game.Overlays.Rankings { public SpotlightsDropdownHeader() { - Height = 48; + AutoSizeAxes = Axes.Y; Text.Font = OsuFont.GetFont(size: 15); + Text.Padding = new MarginPadding { Vertical = 1.5f }; // osu-web line-height difference compensation Foreground.Padding = new MarginPadding { Horizontal = 10, Vertical = 15 }; Margin = Icon.Margin = new MarginPadding(0); } From df328dd9817c82c776b6211e710f177091637017 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 5 Mar 2020 23:09:51 +0100 Subject: [PATCH 0098/1621] Revert margin changes --- osu.Game/Overlays/Rankings/CountryFilter.cs | 4 ++-- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Rankings/CountryFilter.cs b/osu.Game/Overlays/Rankings/CountryFilter.cs index ab5df80d8a..9950f36141 100644 --- a/osu.Game/Overlays/Rankings/CountryFilter.cs +++ b/osu.Game/Overlays/Rankings/CountryFilter.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Rankings Origin = Anchor.CentreLeft, Direction = FillDirection.Horizontal, Spacing = new Vector2(10, 0), - Margin = new MarginPadding { Left = OverlayHeader.CONTENT_X_MARGIN }, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, Children = new Drawable[] { new OsuSpriteText diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index d2cb94de9a..a9fcac42af 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Rankings new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = OverlayHeader.CONTENT_X_MARGIN }, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN }, Children = new Drawable[] { dropdown = new SpotlightsDropdown From b6aedb22d8a5b169b23f528bd3430dfc82162074 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Sun, 15 Mar 2020 01:25:02 +0100 Subject: [PATCH 0099/1621] 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 1c69e4eb5b56246e91e4c89a96c9639c848e6007 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Sun, 15 Mar 2020 18:26:33 +0100 Subject: [PATCH 0100/1621] Update SpotlightSelector design --- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index a9fcac42af..7299a48483 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -77,11 +77,11 @@ namespace osu.Game.Overlays.Rankings }, new FillFlowContainer { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(15, 0), + Spacing = new Vector2(20, 0), Children = new Drawable[] { startDateColumn = new InfoColumn(@"Start Date"), From 280a009784707a6a60586d89fe8bf7d53a998a8d Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Sun, 15 Mar 2020 18:31:44 +0100 Subject: [PATCH 0101/1621] Fix header colours being flipped --- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index eb2b676500..65ef7dc6b7 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -125,7 +125,7 @@ namespace osu.Game.Overlays.Rankings.Tables [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - if (isHighlighted) + if (!isHighlighted) Colour = colourProvider.Foreground1; } } From f1e54d2745c62e85ba06ec01a58b237ea673e2a0 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Sun, 15 Mar 2020 18:32:12 +0100 Subject: [PATCH 0102/1621] Apply suggestions --- osu.Game/Overlays/Rankings/Tables/CountriesTable.cs | 4 ++-- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 011a8207a0..c5e413c7fa 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -73,7 +73,7 @@ namespace osu.Game.Overlays.Rankings.Tables RelativeSizeAxes = Axes.Y; TextAnchor = Anchor.CentreLeft; - if (country.FullName != null) + if (!string.IsNullOrEmpty(country.FullName)) AddLink(country.FullName, () => rankings?.ShowCountry(country)); } } diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 65ef7dc6b7..17d0c9cf24 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -76,7 +76,10 @@ namespace osu.Game.Overlays.Rankings.Tables var isHighlighted = HighlightedColumn() == title; var isGrade = GradeColumns().Contains(title); - return new HeaderText(title, isHighlighted) { Margin = new MarginPadding { Vertical = 5, Horizontal = isGrade ? 20 : 10 } }; + return new HeaderText(title, isHighlighted) + { + Margin = new MarginPadding { Vertical = 5, Horizontal = isGrade ? 20 : 10 } + }; } protected abstract Country GetCountry(TModel item); From d9aef6f813d9666e18dbe313498317fea200dbb4 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Sun, 15 Mar 2020 18:53:40 +0100 Subject: [PATCH 0103/1621] 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 0104/1621] 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 0105/1621] 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 0106/1621] 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 db55b98ed338b4c9c20d4c3a0ee593c7cae0db6a Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Sun, 15 Mar 2020 22:03:54 +0100 Subject: [PATCH 0107/1621] Move grade column spacing logic to UserBasedTable --- .../Overlays/Rankings/Tables/RankingsTable.cs | 17 +++------- .../Overlays/Rankings/Tables/ScoresTable.cs | 2 +- .../Rankings/Tables/UserBasedTable.cs | 31 ++++++++++++++----- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 17d0c9cf24..3fb8602cbc 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -70,16 +70,12 @@ namespace osu.Game.Overlays.Rankings.Tables protected abstract Drawable[] CreateAdditionalContent(TModel item); + protected virtual string HighlightedColumn => @"Performance"; + protected override Drawable CreateHeader(int index, TableColumn column) { var title = column?.Header ?? string.Empty; - var isHighlighted = HighlightedColumn() == title; - var isGrade = GradeColumns().Contains(title); - - return new HeaderText(title, isHighlighted) - { - Margin = new MarginPadding { Vertical = 5, Horizontal = isGrade ? 20 : 10 } - }; + return new HeaderText(title, title == HighlightedColumn); } protected abstract Country GetCountry(TModel item); @@ -109,11 +105,7 @@ namespace osu.Game.Overlays.Rankings.Tables } }; - protected virtual IEnumerable GradeColumns() => new List(); - - protected virtual string HighlightedColumn() => @"Performance"; - - private class HeaderText : OsuSpriteText + protected class HeaderText : OsuSpriteText { private readonly bool isHighlighted; @@ -123,6 +115,7 @@ namespace osu.Game.Overlays.Rankings.Tables Text = text; Font = OsuFont.GetFont(size: 12); + Margin = new MarginPadding { Vertical = 5, Horizontal = 10 }; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs index 370ee506c2..9fae8e1897 100644 --- a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs @@ -33,6 +33,6 @@ namespace osu.Game.Overlays.Rankings.Tables } }; - protected override string HighlightedColumn() => @"Ranked Score"; + protected override string HighlightedColumn => @"Ranked Score"; } } diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index 8ce31c8539..a6969f483f 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -19,17 +19,20 @@ namespace osu.Game.Overlays.Rankings.Tables { } - protected override IEnumerable GradeColumns() => new List { "SS", "S", "A" }; + protected virtual IEnumerable GradeColumns => new List { "SS", "S", "A" }; - protected override TableColumn[] CreateAdditionalHeaders() - { - var gradeColumns = GradeColumns().Select(grade => new TableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize))); - - return new[] + protected override TableColumn[] CreateAdditionalHeaders() => new[] { new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - }.Concat(CreateUniqueHeaders()).Concat(gradeColumns).ToArray(); + }.Concat(CreateUniqueHeaders()) + .Concat(GradeColumns.Select(grade => new TableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)))) + .ToArray(); + + protected override Drawable CreateHeader(int index, TableColumn column) + { + var title = column?.Header ?? string.Empty; + return new UserTableHeaderText(title, HighlightedColumn == title, GradeColumns.Contains(title)); } protected sealed override Country GetCountry(UserStatistics item) => item.User.Country; @@ -60,5 +63,19 @@ namespace osu.Game.Overlays.Rankings.Tables protected abstract TableColumn[] CreateUniqueHeaders(); protected abstract Drawable[] CreateUniqueContent(UserStatistics item); + + private class UserTableHeaderText : HeaderText + { + public UserTableHeaderText(string text, bool isHighlighted, bool isGrade) + : base(text, isHighlighted) + { + Margin = new MarginPadding + { + // Grade columns have extra horizontal padding for readibility + Horizontal = isGrade ? 20 : 10, + Vertical = 5 + }; + } + } } } From 405030da471b6388d015b941a04cb466b2def9da Mon Sep 17 00:00:00 2001 From: BlauFx Date: Mon, 16 Mar 2020 20:34:53 +0100 Subject: [PATCH 0108/1621] 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 4ecf2c0512ff830b69d4d62483ab664e4a42eb24 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 18 Mar 2020 14:19:15 +0800 Subject: [PATCH 0109/1621] remove unused property --- osu.Game/Overlays/BeatmapSet/Info.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index c789b06dd6..c69684f314 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -142,7 +142,6 @@ namespace osu.Game.Overlays.BeatmapSet private class MetadataSection : FillFlowContainer { private readonly MetadataType type; - private readonly OsuSpriteText header; private readonly LinkFlowContainer textFlow; public string Text From 92cab12eb7425179fb2277d3428cf99fc3617332 Mon Sep 17 00:00:00 2001 From: Jess Meng Date: Fri, 11 Dec 2020 21:52:36 -0800 Subject: [PATCH 0110/1621] Added ctrl + w and ctrl + t keyboard shortcuts --- osu.Game/Overlays/ChatOverlay.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 8bc7e21047..5162b6aab0 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -25,6 +25,9 @@ using osuTK.Input; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using System; +using osu.Game.Input.Bindings; + namespace osu.Game.Overlays { public class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent @@ -333,6 +336,16 @@ namespace osu.Game.Overlays ChannelTabControl.Current.Value = channel; } + private void selectChannelSelectorTab() + { + int i = 0; + var channel = ChannelTabControl.Items + .Where(tab => (tab is ChannelSelectorTabItem.ChannelSelectorTabChannel)) + .ElementAtOrDefault(i); + if (channel != null) + ChannelTabControl.Current.Value = channel; + } + protected override bool OnKeyDown(KeyDownEvent e) { if (e.AltPressed) @@ -356,6 +369,18 @@ namespace osu.Game.Overlays return true; } } + if (e.ControlPressed) + { + switch (e.Key) + { + case Key.W: + channelManager.LeaveChannel(channelManager.CurrentChannel.Value); + return true; + case Key.T: + selectChannelSelectorTab(); + return true; + } + } return base.OnKeyDown(e); } @@ -392,6 +417,7 @@ namespace osu.Game.Overlays private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) { + switch (args.Action) { case NotifyCollectionChangedAction.Add: From 3dd6589d6e2d516ec5790ce06c24bb5d98447b31 Mon Sep 17 00:00:00 2001 From: Jess Meng Date: Sat, 12 Dec 2020 15:53:08 -0800 Subject: [PATCH 0111/1621] Fixed style changes and cleaned up ctrl + t implementation --- .../Overlays/Chat/Tabs/ChannelTabControl.cs | 5 +++++ osu.Game/Overlays/ChatOverlay.cs | 19 +++---------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 19c6f437b6..bc68a9f32a 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -96,6 +96,11 @@ namespace osu.Game.Overlays.Chat.Tabs selectorTab.Active.Value = false; } + public void SelectChannelSelectorTab() + { + SelectTab(selectorTab); + } + protected override TabFillFlowContainer CreateTabFlow() => new ChannelTabFillFlowContainer { Direction = FillDirection.Full, diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 5162b6aab0..7ff63595c7 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -25,9 +25,6 @@ using osuTK.Input; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using System; -using osu.Game.Input.Bindings; - namespace osu.Game.Overlays { public class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent @@ -335,17 +332,6 @@ namespace osu.Game.Overlays if (channel != null) ChannelTabControl.Current.Value = channel; } - - private void selectChannelSelectorTab() - { - int i = 0; - var channel = ChannelTabControl.Items - .Where(tab => (tab is ChannelSelectorTabItem.ChannelSelectorTabChannel)) - .ElementAtOrDefault(i); - if (channel != null) - ChannelTabControl.Current.Value = channel; - } - protected override bool OnKeyDown(KeyDownEvent e) { if (e.AltPressed) @@ -369,6 +355,7 @@ namespace osu.Game.Overlays return true; } } + if (e.ControlPressed) { switch (e.Key) @@ -376,8 +363,9 @@ namespace osu.Game.Overlays case Key.W: channelManager.LeaveChannel(channelManager.CurrentChannel.Value); return true; + case Key.T: - selectChannelSelectorTab(); + ChannelTabControl.SelectChannelSelectorTab(); return true; } } @@ -417,7 +405,6 @@ namespace osu.Game.Overlays private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) { - switch (args.Action) { case NotifyCollectionChangedAction.Add: From 4c1e75f1017b3b5a5aa01f4f07f9ebcd44bfed5e Mon Sep 17 00:00:00 2001 From: Angela Zhang <43687061+angelaz1@users.noreply.github.com> Date: Sat, 12 Dec 2020 18:02:08 -0600 Subject: [PATCH 0112/1621] Small whitespace fix --- osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index bc68a9f32a..c314e10aff 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Chat.Tabs selectorTab.Active.Value = false; } - public void SelectChannelSelectorTab() + public void SelectChannelSelectorTab() { SelectTab(selectorTab); } From b9f687d7f9605f97d8494e9e6a3e6545aa5a9c24 Mon Sep 17 00:00:00 2001 From: Angela Zhang <43687061+angelaz1@users.noreply.github.com> Date: Sat, 12 Dec 2020 18:55:17 -0600 Subject: [PATCH 0113/1621] Adding in extra blank line --- osu.Game/Overlays/ChatOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 7ff63595c7..1b1b7ec6e8 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -332,6 +332,7 @@ namespace osu.Game.Overlays if (channel != null) ChannelTabControl.Current.Value = channel; } + protected override bool OnKeyDown(KeyDownEvent e) { if (e.AltPressed) From 084e4ce50bb4265c5fd020c47e9a41a08fc9dc0f Mon Sep 17 00:00:00 2001 From: Angela Zhang <43687061+angelaz1@users.noreply.github.com> Date: Sat, 12 Dec 2020 20:11:57 -0600 Subject: [PATCH 0114/1621] Removing whitespace --- 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 1b1b7ec6e8..b310e3ff4c 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -332,7 +332,7 @@ namespace osu.Game.Overlays if (channel != null) ChannelTabControl.Current.Value = channel; } - + protected override bool OnKeyDown(KeyDownEvent e) { if (e.AltPressed) From 2d98da0d61374a2abedb3c1daae108df0e77d905 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Sun, 13 Dec 2020 13:21:50 -0500 Subject: [PATCH 0115/1621] Untested Ctrl+Shift+T shortcut prototype Added a list to the ChannelManager class that tracks which tabs I closed. Works like a stack, where it adds to the end every time I close a tab. Then added a function that uses this list to open the last closed channel, and added a shortcut inside of ChatOverlay, similar to how jmeng implemented shortcuts. Code is currently untested. --- osu.Game/Online/Chat/ChannelManager.cs | 30 ++++++++++++++++++++++++++ osu.Game/Overlays/ChatOverlay.cs | 26 ++++++++++++++++------ 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 16f46581c5..9c47c94677 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -33,6 +33,19 @@ namespace osu.Game.Online.Chat private readonly BindableList availableChannels = new BindableList(); private readonly BindableList joinedChannels = new BindableList(); + // Prototype for keeping a list of closed channels in an + // order so we can figure out the reverse order of how channels + // were close + // Bindable list supports an insert at indexc function and a + // remove function. If the list re-indexes after each remove (I can + // check the behaviour of the C# List System.Collections.Generic library to confirm this, since that + // library appears to be what is used underneath), then I can just always add at the end + // of the list and always remove index 0 (if size > 0) + // A stack exchange post indicates that List remove will decrement all the + // indeces after the node we removed + + private readonly BindableList closedChannels = new BindableList(); + /// /// The currently opened channel /// @@ -48,6 +61,11 @@ namespace osu.Game.Online.Chat /// public IBindableList AvailableChannels => availableChannels; + /// + /// The channels available for the player to join + /// + public IBindableList ClosedChannels => ClosedChannels; + [Resolved] private IAPIProvider api { get; set; } @@ -407,6 +425,8 @@ namespace osu.Game.Online.Chat CurrentChannel.Value = null; joinedChannels.Remove(channel); + // insert at the end of the list + closedChannels.Insert(closedChannels.Count, channel); if (channel.Joined.Value) { @@ -415,6 +435,16 @@ namespace osu.Game.Online.Chat } } + + public void JoinLastClosedChannel() + { + if(joinedChannels.Count == 0) + return; + Channel lastClosedChannel = joinedChannels[joinedChannels.Count - 1]; + JoinChannel(lastClosedChannel); + joinedChannels.Remove(lastClosedChannel); + } + private long lastMessageId; private bool channelsInitialised; diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index b310e3ff4c..0adf3cb2c5 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -359,15 +359,27 @@ namespace osu.Game.Overlays if (e.ControlPressed) { - switch (e.Key) + if(e.ShiftPressed) { - case Key.W: - channelManager.LeaveChannel(channelManager.CurrentChannel.Value); - return true; + switch(e.Key) + { + case Key.T: + channelManager.JoinLastClosedChannel(); + return true; + } + } + else + { + switch (e.Key) + { + case Key.W: + channelManager.LeaveChannel(channelManager.CurrentChannel.Value); + return true; - case Key.T: - ChannelTabControl.SelectChannelSelectorTab(); - return true; + case Key.T: + ChannelTabControl.SelectChannelSelectorTab(); + return true; + } } } From 5481ba43c7875e6bb8df292813c8de23dabe7738 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Sun, 13 Dec 2020 14:14:57 -0500 Subject: [PATCH 0116/1621] Fixed a bug where Ctrl+Shift+t shortcut was using the wrong list The JoinLastClosedChannel code was using the joinedChannels list instead of the closedChannels list. Fixing this bug made the Ctrl+Shift+t shortuct work as expected. --- osu.Game/Online/Chat/ChannelManager.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 9c47c94677..b472f521d7 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -435,14 +435,13 @@ namespace osu.Game.Online.Chat } } - public void JoinLastClosedChannel() { - if(joinedChannels.Count == 0) + if(closedChannels.Count == 0) return; - Channel lastClosedChannel = joinedChannels[joinedChannels.Count - 1]; + Channel lastClosedChannel = closedChannels[closedChannels.Count - 1]; JoinChannel(lastClosedChannel); - joinedChannels.Remove(lastClosedChannel); + closedChannels.Remove(lastClosedChannel); } private long lastMessageId; From 2863187b16013c2db36c04d8512355d128cc525d Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Sun, 13 Dec 2020 20:46:02 -0500 Subject: [PATCH 0117/1621] Changing behvaior for channels that have already been reopened Ctrl+Shift+t will now skip trying to reopen any channels that are already open, and will instead attempt to open the next still-closed channel. --- osu.Game/Online/Chat/ChannelManager.cs | 54 +++++++++++++++++--------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index b472f521d7..cf7f5604d9 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -33,19 +33,15 @@ namespace osu.Game.Online.Chat private readonly BindableList availableChannels = new BindableList(); private readonly BindableList joinedChannels = new BindableList(); - // Prototype for keeping a list of closed channels in an - // order so we can figure out the reverse order of how channels - // were close - // Bindable list supports an insert at indexc function and a - // remove function. If the list re-indexes after each remove (I can - // check the behaviour of the C# List System.Collections.Generic library to confirm this, since that - // library appears to be what is used underneath), then I can just always add at the end - // of the list and always remove index 0 (if size > 0) - // A stack exchange post indicates that List remove will decrement all the - // indeces after the node we removed + // Keeps a list of closed channels. More recently closed channels appear at higher indeces private readonly BindableList closedChannels = new BindableList(); + // For efficiency purposes, this constant bounds the number of closed channels we store. + // This number is somewhat arbitrary; future developers are free to modify it. + // Must be a positive number. + private const int closedChannelsMaxSize = 50; + /// /// The currently opened channel /// @@ -61,11 +57,6 @@ namespace osu.Game.Online.Chat /// public IBindableList AvailableChannels => availableChannels; - /// - /// The channels available for the player to join - /// - public IBindableList ClosedChannels => ClosedChannels; - [Resolved] private IAPIProvider api { get; set; } @@ -425,7 +416,15 @@ namespace osu.Game.Online.Chat CurrentChannel.Value = null; joinedChannels.Remove(channel); - // insert at the end of the list + + // Prevent the closedChannel list from exceeding the max size + // by removing the oldest element + if(closedChannels.Count >= closedChannelsMaxSize) + { + closedChannels.Remove(closedChannels[0]); + } + + // insert at the end of the closedChannels list closedChannels.Insert(closedChannels.Count, channel); if (channel.Joined.Value) @@ -435,13 +434,32 @@ namespace osu.Game.Online.Chat } } + + + /// + /// Opens the most recently closed channel that has not + /// already been reopened + /// Works similarly to reopening last closed tab on a web browser. + /// public void JoinLastClosedChannel() { - if(closedChannels.Count == 0) + if(closedChannels.Count <= 0) + { return; + } + Channel lastClosedChannel = closedChannels[closedChannels.Count - 1]; - JoinChannel(lastClosedChannel); closedChannels.Remove(lastClosedChannel); + // If the user already joined the channel, try the next + // channel in the list + if(joinedChannels.IndexOf(lastClosedChannel) >= 0) + { + JoinLastClosedChannel(); + } + else + { + JoinChannel(lastClosedChannel); + } } private long lastMessageId; From 08a2cdaf8d43b7efe9a44d4c9833242abd261c60 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Sun, 13 Dec 2020 21:02:35 -0500 Subject: [PATCH 0118/1621] Minor formatting changes --- osu.Game/Online/Chat/ChannelManager.cs | 10 +++++----- osu.Game/Overlays/ChatOverlay.cs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index cf7f5604d9..e1f4d82e52 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -419,7 +419,7 @@ namespace osu.Game.Online.Chat // Prevent the closedChannel list from exceeding the max size // by removing the oldest element - if(closedChannels.Count >= closedChannelsMaxSize) + if (closedChannels.Count >= closedChannelsMaxSize) { closedChannels.Remove(closedChannels[0]); } @@ -435,15 +435,15 @@ namespace osu.Game.Online.Chat } - + /// /// Opens the most recently closed channel that has not /// already been reopened - /// Works similarly to reopening last closed tab on a web browser. + /// Works similarly to reopening the last closed tab on a web browser. /// public void JoinLastClosedChannel() { - if(closedChannels.Count <= 0) + if (closedChannels.Count <= 0) { return; } @@ -452,7 +452,7 @@ namespace osu.Game.Online.Chat closedChannels.Remove(lastClosedChannel); // If the user already joined the channel, try the next // channel in the list - if(joinedChannels.IndexOf(lastClosedChannel) >= 0) + if (joinedChannels.IndexOf(lastClosedChannel) >= 0) { JoinLastClosedChannel(); } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 0adf3cb2c5..12bd7e895e 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -359,9 +359,9 @@ namespace osu.Game.Overlays if (e.ControlPressed) { - if(e.ShiftPressed) + if (e.ShiftPressed) { - switch(e.Key) + switch (e.Key) { case Key.T: channelManager.JoinLastClosedChannel(); From 337309ec1332766fd314c8bf29f3d2790933dd28 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Sun, 13 Dec 2020 22:06:59 -0500 Subject: [PATCH 0119/1621] Added Tests for the Ctrl+Shift+T shortcut Tests have not actually been run yet. --- .../Visual/Online/TestSceneChatOverlay.cs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index fca642ad6c..1770c34d2f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -208,6 +208,84 @@ namespace osu.Game.Tests.Visual.Online AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } + // Control-Shift-T should reopen the most recently closed tab that has not + // already been reopened + private void pressControlShiftT() + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.T); + InputManager.ReleaseKey(Key.ControlLeft); + InputManager.ReleaseKey(Key.ShiftLeft); + } + + public void TestCtrlShiftTShortcut1() + { + AddStep("Join 2 channels", () => + { + channelManager.JoinChannel(channel1); + channelManager.JoinChannel(channel2); + }); + + // Close channel 2 + AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); + AddStep("Click channel 2 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + + AddStep("Press Control-Shift-T", () => pressControlShiftT()); + + // Channel 2 should now be open again + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + } + + public void TestCtrlShiftTShortcut2() + { + AddStep("Join 2 channels", () => + { + channelManager.JoinChannel(channel1); + channelManager.JoinChannel(channel2); + }); + + // Close channels 1 and channel 2 + AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); + AddStep("Click channel 2 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); + + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + AddStep("Click channel 1 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); + + AddAssert("channel 1 closed", () => !channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + + AddStep("Press Control-Shift-T", () => pressControlShiftT()); + // Channel 1 should now be open again. Channel 2 should not + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + + AddStep("Press Control-Shift-T", () => pressControlShiftT()); + + // Both Channel 1 and Channel 2 should be open + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + + // Processing this again should have not effect + AddStep("Press Control-Shift-T", () => pressControlShiftT()); + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + + // Close channel 2 again + AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); + AddStep("Click channel 2 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); + + // Both channel 1 and channel 2 should be open + AddStep("Press Control-Shift-T", () => pressControlShiftT()); + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + } + + + private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From 3301f532eed41ef457b3be009e9c9ca2c44773ca Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Sun, 13 Dec 2020 23:27:48 -0500 Subject: [PATCH 0120/1621] Additional formatting Ran the dotnet format as well as the .\InspectCode.ps1 tools, and fixed the style issues they found. --- .../Visual/Online/TestSceneChatOverlay.cs | 22 +++++++++---------- osu.Game/Online/Chat/ChannelManager.cs | 12 +++++----- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 1770c34d2f..fddaaa8215 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -212,11 +212,11 @@ namespace osu.Game.Tests.Visual.Online // already been reopened private void pressControlShiftT() { - InputManager.PressKey(Key.ControlLeft); - InputManager.PressKey(Key.ShiftLeft); - InputManager.Key(Key.T); - InputManager.ReleaseKey(Key.ControlLeft); - InputManager.ReleaseKey(Key.ShiftLeft); + AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); + AddStep("Press and hold Shift", () => InputManager.PressKey(Key.ShiftLeft)); + AddStep("Press T", () => InputManager.Key(Key.T)); + AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); + AddStep("Release Shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); } public void TestCtrlShiftTShortcut1() @@ -233,7 +233,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); - AddStep("Press Control-Shift-T", () => pressControlShiftT()); + pressControlShiftT(); // Channel 2 should now be open again AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); @@ -258,19 +258,19 @@ namespace osu.Game.Tests.Visual.Online AddAssert("channel 1 closed", () => !channelManager.JoinedChannels.Contains(channel1)); AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); - AddStep("Press Control-Shift-T", () => pressControlShiftT()); + pressControlShiftT(); // Channel 1 should now be open again. Channel 2 should not AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); - AddStep("Press Control-Shift-T", () => pressControlShiftT()); + pressControlShiftT(); // Both Channel 1 and Channel 2 should be open AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); // Processing this again should have not effect - AddStep("Press Control-Shift-T", () => pressControlShiftT()); + pressControlShiftT(); AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); @@ -279,13 +279,11 @@ namespace osu.Game.Tests.Visual.Online AddStep("Click channel 2 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); // Both channel 1 and channel 2 should be open - AddStep("Press Control-Shift-T", () => pressControlShiftT()); + pressControlShiftT(); AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); } - - private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index e1f4d82e52..63eea0b190 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -33,14 +33,13 @@ namespace osu.Game.Online.Chat private readonly BindableList availableChannels = new BindableList(); private readonly BindableList joinedChannels = new BindableList(); - // Keeps a list of closed channels. More recently closed channels appear at higher indeces private readonly BindableList closedChannels = new BindableList(); // For efficiency purposes, this constant bounds the number of closed channels we store. // This number is somewhat arbitrary; future developers are free to modify it. // Must be a positive number. - private const int closedChannelsMaxSize = 50; + private const int closed_channels_max_size = 50; /// /// The currently opened channel @@ -419,7 +418,7 @@ namespace osu.Game.Online.Chat // Prevent the closedChannel list from exceeding the max size // by removing the oldest element - if (closedChannels.Count >= closedChannelsMaxSize) + if (closedChannels.Count >= closed_channels_max_size) { closedChannels.Remove(closedChannels[0]); } @@ -434,8 +433,6 @@ namespace osu.Game.Online.Chat } } - - /// /// Opens the most recently closed channel that has not /// already been reopened @@ -448,8 +445,11 @@ namespace osu.Game.Online.Chat return; } - Channel lastClosedChannel = closedChannels[closedChannels.Count - 1]; + // equivalent to Channel lastClosedChannel = closedChannels[closedChannels.Count - 1]; + Channel lastClosedChannel = closedChannels[^1]; + closedChannels.Remove(lastClosedChannel); + // If the user already joined the channel, try the next // channel in the list if (joinedChannels.IndexOf(lastClosedChannel) >= 0) From 35f403dacd6b8999a7d51c90d9d176eb2a4fdc88 Mon Sep 17 00:00:00 2001 From: Jess Meng Date: Sun, 13 Dec 2020 22:05:54 -0800 Subject: [PATCH 0121/1621] Added tests for ctrl + w and ctrl + t --- .../Visual/Online/TestSceneChatOverlay.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index fca642ad6c..e80bab407e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -207,6 +207,62 @@ namespace osu.Game.Tests.Visual.Online AddStep("Click normal close button", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } + + private void pressControlW() + { + AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); + AddStep("Press W", () => InputManager.Key(Key.W)); + AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); + } + + + public void TestCtrlWShortcut() + { + AddStep("Join 2 channels", () => + { + channelManager.JoinChannel(channel1); + channelManager.JoinChannel(channel2); + }); + + // Want to close channel 2 + AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); + + pressControlW(); + + // Channel 2 should be closed + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + + // Want to close channel 1 + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + pressControlW(); + + // Channel 1 and channel 2 should be closed + AddAssert("channel 1 closed", () => !channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + } + + private void pressControlT() + { + AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); + AddStep("Press T", () => InputManager.Key(Key.T)); + AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); + } + + public void TestCtrlTShortcut() + { + AddStep("Join 2 channels", () => + { + channelManager.JoinChannel(channel1); + channelManager.JoinChannel(channel2); + }); + + // Want to join another channel + pressControlT(); + // Selector should be visible + AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); + } private void pressChannelHotkey(int number) { From ccbf6db810be69444c87c9ffa91d139730e2bea3 Mon Sep 17 00:00:00 2001 From: Jess Meng Date: Sun, 13 Dec 2020 22:11:52 -0800 Subject: [PATCH 0122/1621] Fixed some formatting and style --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index e80bab407e..44901992e7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -214,8 +214,8 @@ namespace osu.Game.Tests.Visual.Online AddStep("Press W", () => InputManager.Key(Key.W)); AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); } - + [Test] public void TestCtrlWShortcut() { AddStep("Join 2 channels", () => @@ -250,6 +250,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); } + [Test] public void TestCtrlTShortcut() { AddStep("Join 2 channels", () => From 5903569386a7bd35f6abb075c5bf5331965c5414 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Mon, 14 Dec 2020 13:00:02 -0500 Subject: [PATCH 0123/1621] Changing the Tests --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index fddaaa8215..d4cc60ba97 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -255,8 +255,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); AddStep("Click channel 1 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); - AddAssert("channel 1 closed", () => !channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any(); pressControlShiftT(); // Channel 1 should now be open again. Channel 2 should not From b26946ba8e38f50ec64cc10943d505d087e879cd Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Mon, 14 Dec 2020 13:09:34 -0500 Subject: [PATCH 0124/1621] Temporarily removed test cases I did this to try to figure out what was failing on pull requests --- .../Visual/Online/TestSceneChatOverlay.cs | 64 ------------------- 1 file changed, 64 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index d4cc60ba97..d444c2e9f1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -219,70 +219,6 @@ namespace osu.Game.Tests.Visual.Online AddStep("Release Shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); } - public void TestCtrlShiftTShortcut1() - { - AddStep("Join 2 channels", () => - { - channelManager.JoinChannel(channel1); - channelManager.JoinChannel(channel2); - }); - - // Close channel 2 - AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); - AddStep("Click channel 2 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); - - pressControlShiftT(); - - // Channel 2 should now be open again - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - } - - public void TestCtrlShiftTShortcut2() - { - AddStep("Join 2 channels", () => - { - channelManager.JoinChannel(channel1); - channelManager.JoinChannel(channel2); - }); - - // Close channels 1 and channel 2 - AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); - AddStep("Click channel 2 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); - - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - AddStep("Click channel 1 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); - - AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any(); - - pressControlShiftT(); - // Channel 1 should now be open again. Channel 2 should not - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); - - pressControlShiftT(); - - // Both Channel 1 and Channel 2 should be open - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - - // Processing this again should have not effect - pressControlShiftT(); - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - - // Close channel 2 again - AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); - AddStep("Click channel 2 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); - - // Both channel 1 and channel 2 should be open - pressControlShiftT(); - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - } - private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From bd2765ecc4fe8900ccf60a693b1b30e33635cdb4 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Mon, 14 Dec 2020 13:23:43 -0500 Subject: [PATCH 0125/1621] Fixed style concerns --- osu.Game/Online/Chat/ChannelManager.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 63eea0b190..607c9a1223 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -420,7 +420,7 @@ namespace osu.Game.Online.Chat // by removing the oldest element if (closedChannels.Count >= closed_channels_max_size) { - closedChannels.Remove(closedChannels[0]); + closedChannels.RemoveAt(0); } // insert at the end of the closedChannels list @@ -445,8 +445,7 @@ namespace osu.Game.Online.Chat return; } - // equivalent to Channel lastClosedChannel = closedChannels[closedChannels.Count - 1]; - Channel lastClosedChannel = closedChannels[^1]; + Channel lastClosedChannel = closedChannels.Last(); closedChannels.Remove(lastClosedChannel); From 9cb0ed3b8f42716db57540e78c11f5326faabfc3 Mon Sep 17 00:00:00 2001 From: Jess Meng Date: Mon, 14 Dec 2020 12:38:59 -0800 Subject: [PATCH 0126/1621] Fixed formatting for tests --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 44901992e7..53577f41ee 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -207,18 +207,16 @@ namespace osu.Game.Tests.Visual.Online AddStep("Click normal close button", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } - private void pressControlW() { AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); AddStep("Press W", () => InputManager.Key(Key.W)); AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); } - [Test] public void TestCtrlWShortcut() { - AddStep("Join 2 channels", () => + AddStep("Join 2 channels", () => { channelManager.JoinChannel(channel1); channelManager.JoinChannel(channel2); @@ -228,7 +226,6 @@ namespace osu.Game.Tests.Visual.Online AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); pressControlW(); - // Channel 2 should be closed AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); @@ -237,10 +234,8 @@ namespace osu.Game.Tests.Visual.Online AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); pressControlW(); - // Channel 1 and channel 2 should be closed - AddAssert("channel 1 closed", () => !channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } private void pressControlT() @@ -253,7 +248,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestCtrlTShortcut() { - AddStep("Join 2 channels", () => + AddStep("Join 2 channels", () => { channelManager.JoinChannel(channel1); channelManager.JoinChannel(channel2); From 61fe6ca84e91ddccfc80b5d20baf4b6f233cd6a9 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Mon, 14 Dec 2020 15:47:48 -0500 Subject: [PATCH 0127/1621] Added back CI tests --- .../Visual/Online/TestSceneChatOverlay.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index d444c2e9f1..adeeae8652 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -219,6 +219,70 @@ namespace osu.Game.Tests.Visual.Online AddStep("Release Shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); } + public void TestCtrlShiftTShortcut1() + { + AddStep("Join 2 channels", () => + { + channelManager.JoinChannel(channel1); + channelManager.JoinChannel(channel2); + }); + + // Close channel 2 + AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); + AddStep("Click channel 2 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + + pressControlShiftT(); + + // Channel 2 should now be open again + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + } + + public void TestCtrlShiftTShortcut2() + { + AddStep("Join 2 channels", () => + { + channelManager.JoinChannel(channel1); + channelManager.JoinChannel(channel2); + }); + + // Close channels 1 and channel 2 + AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); + AddStep("Click channel 2 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); + + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + AddStep("Click channel 1 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); + + AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); + + pressControlShiftT(); + // Channel 1 should now be open again. Channel 2 should not + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + + pressControlShiftT(); + + // Both Channel 1 and Channel 2 should be open + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + + // Processing this again should have not effect + pressControlShiftT(); + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + + // Close channel 2 again + AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); + AddStep("Click channel 2 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); + + // Both channel 1 and channel 2 should be open + pressControlShiftT(); + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + } + private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From 1af0f79ddf4b1af252539164198fd1947982d072 Mon Sep 17 00:00:00 2001 From: Angela Zhang <43687061+angelaz1@users.noreply.github.com> Date: Mon, 14 Dec 2020 16:58:43 -0600 Subject: [PATCH 0128/1621] Adding newline style fixes --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 53577f41ee..d39f0678c8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -207,12 +207,14 @@ namespace osu.Game.Tests.Visual.Online AddStep("Click normal close button", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } + private void pressControlW() { AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); AddStep("Press W", () => InputManager.Key(Key.W)); AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); } + [Test] public void TestCtrlWShortcut() { From 206312d4c2feafe29b1a8c0bdf927126f40bbd2e Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Mon, 14 Dec 2020 18:26:57 -0500 Subject: [PATCH 0129/1621] Changed the CI tests I'm trying to make changes because something is going wrong with theGitHub CI tests. --- .../Visual/Online/TestSceneChatOverlay.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index adeeae8652..556b42390e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -228,8 +228,7 @@ namespace osu.Game.Tests.Visual.Online }); // Close channel 2 - AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); - AddStep("Click channel 2 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); + AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); @@ -249,12 +248,8 @@ namespace osu.Game.Tests.Visual.Online }); // Close channels 1 and channel 2 - AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); - AddStep("Click channel 2 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); - - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - AddStep("Click channel 1 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); - + AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); + AddStep("Leave channel 1", () => channelManager.LeaveChannel(channel1)); AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); pressControlShiftT(); @@ -274,8 +269,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); // Close channel 2 again - AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); - AddStep("Click channel 2 close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); + AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); // Both channel 1 and channel 2 should be open pressControlShiftT(); From 017e00eb268eeaa176d18b128f20a9f53a39ee9c Mon Sep 17 00:00:00 2001 From: Jess Meng Date: Mon, 14 Dec 2020 15:27:57 -0800 Subject: [PATCH 0130/1621] More style fixes --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index d39f0678c8..3dfa5d09be 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -207,14 +207,12 @@ namespace osu.Game.Tests.Visual.Online AddStep("Click normal close button", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } - private void pressControlW() { AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); AddStep("Press W", () => InputManager.Key(Key.W)); AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); - } - + } [Test] public void TestCtrlWShortcut() { From 37a601088786d3dcdccc85adc04195acf4f74edd Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Mon, 14 Dec 2020 18:33:51 -0500 Subject: [PATCH 0131/1621] Changed test formatting Tests should be prefixed with [Test]. Previously my tests do not have this. --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 556b42390e..3eaba131ad 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -219,6 +219,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Release Shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); } + [Test] public void TestCtrlShiftTShortcut1() { AddStep("Join 2 channels", () => @@ -239,6 +240,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); } + [Test] public void TestCtrlShiftTShortcut2() { AddStep("Join 2 channels", () => From 7fe7c24ce15cd1ccd0c19020b9d38d26960e4ca8 Mon Sep 17 00:00:00 2001 From: Jess Meng Date: Mon, 14 Dec 2020 15:44:40 -0800 Subject: [PATCH 0132/1621] Style fixes for whitespaces --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 3dfa5d09be..68fc8ac8c0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -207,12 +207,14 @@ namespace osu.Game.Tests.Visual.Online AddStep("Click normal close button", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } + private void pressControlW() { AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); AddStep("Press W", () => InputManager.Key(Key.W)); AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); - } + } + [Test] public void TestCtrlWShortcut() { From bd2e42e7867153fb88851e7defb5fe7ec3828665 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Mon, 14 Dec 2020 19:10:26 -0500 Subject: [PATCH 0133/1621] Toggling out CI tests I'm still trying to track donwn why GitHub won't accept my CI. I'm toggling tests to see what is the problem. --- .../Visual/Online/TestSceneChatOverlay.cs | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 3eaba131ad..fd83f761a6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -240,45 +240,44 @@ namespace osu.Game.Tests.Visual.Online AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); } - [Test] - public void TestCtrlShiftTShortcut2() - { - AddStep("Join 2 channels", () => - { - channelManager.JoinChannel(channel1); - channelManager.JoinChannel(channel2); - }); + // [Test] + // public void TestCtrlShiftTShortcut2() + // { + // AddStep("Join 2 channels", () => + // { + // channelManager.JoinChannel(channel1); + // channelManager.JoinChannel(channel2); + // }); - // Close channels 1 and channel 2 - AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); - AddStep("Leave channel 1", () => channelManager.LeaveChannel(channel1)); - AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); + // // Close channels 1 and channel 2 + // AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); + // AddStep("Leave channel 1", () => channelManager.LeaveChannel(channel1)); + // AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); - pressControlShiftT(); - // Channel 1 should now be open again. Channel 2 should not - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + // pressControlShiftT(); + // // Channel 1 should now be open again. Channel 2 should not + // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + // AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); - pressControlShiftT(); + // pressControlShiftT(); - // Both Channel 1 and Channel 2 should be open - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + // // Both Channel 1 and Channel 2 should be open + // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + // AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - // Processing this again should have not effect - pressControlShiftT(); - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + // // Processing this again should have not effect + // pressControlShiftT(); + // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + // AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - // Close channel 2 again - AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); - - // Both channel 1 and channel 2 should be open - pressControlShiftT(); - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - } + // // Close channel 2 again + // AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); + // // Both channel 1 and channel 2 should be open + // pressControlShiftT(); + // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + // AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + // } private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From f17df1fdf59c71ad3d3e32f2175bbd5e8d3bb575 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Mon, 14 Dec 2020 19:27:04 -0500 Subject: [PATCH 0134/1621] Commented out all CI tests Part of my efforts to narrow down GitHub CI issues --- .../Visual/Online/TestSceneChatOverlay.cs | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index fd83f761a6..43ff5dc78c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -210,36 +210,35 @@ namespace osu.Game.Tests.Visual.Online // Control-Shift-T should reopen the most recently closed tab that has not // already been reopened - private void pressControlShiftT() - { - AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); - AddStep("Press and hold Shift", () => InputManager.PressKey(Key.ShiftLeft)); - AddStep("Press T", () => InputManager.Key(Key.T)); - AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); - AddStep("Release Shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); - } + // private void pressControlShiftT() + // { + // AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); + // AddStep("Press and hold Shift", () => InputManager.PressKey(Key.ShiftLeft)); + // AddStep("Press T", () => InputManager.Key(Key.T)); + // AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); + // AddStep("Release Shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + // } - [Test] - public void TestCtrlShiftTShortcut1() - { - AddStep("Join 2 channels", () => - { - channelManager.JoinChannel(channel1); - channelManager.JoinChannel(channel2); - }); + // [Test] + // public void TestCtrlShiftTShortcut1() + // { + // AddStep("Join 2 channels", () => + // { + // channelManager.JoinChannel(channel1); + // channelManager.JoinChannel(channel2); + // }); - // Close channel 2 - AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + // // Close channel 2 + // AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); + // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + // AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); - pressControlShiftT(); - - // Channel 2 should now be open again - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - } + // pressControlShiftT(); + // // Channel 2 should now be open again + // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + // AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + // } // [Test] // public void TestCtrlShiftTShortcut2() // { From 9af448d559f465b50921f1f5ed5b5d8cc0abd8de Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Mon, 14 Dec 2020 22:02:34 -0500 Subject: [PATCH 0135/1621] Testing first CI test --- .../Visual/Online/TestSceneChatOverlay.cs | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 43ff5dc78c..ce590839c2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -210,35 +210,36 @@ namespace osu.Game.Tests.Visual.Online // Control-Shift-T should reopen the most recently closed tab that has not // already been reopened - // private void pressControlShiftT() - // { - // AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); - // AddStep("Press and hold Shift", () => InputManager.PressKey(Key.ShiftLeft)); - // AddStep("Press T", () => InputManager.Key(Key.T)); - // AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); - // AddStep("Release Shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); - // } + private void pressControlShiftT() + { + AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); + AddStep("Press and hold Shift", () => InputManager.PressKey(Key.ShiftLeft)); + AddStep("Press T", () => InputManager.Key(Key.T)); + AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); + AddStep("Release Shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + } - // [Test] - // public void TestCtrlShiftTShortcut1() - // { - // AddStep("Join 2 channels", () => - // { - // channelManager.JoinChannel(channel1); - // channelManager.JoinChannel(channel2); - // }); + [Test] + public void TestCtrlShiftTShortcut1() + { + AddStep("Join 2 channels", () => + { + channelManager.JoinChannel(channel1); + channelManager.JoinChannel(channel2); + }); - // // Close channel 2 - // AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); - // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - // AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + // Close channel 2 + AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); - // pressControlShiftT(); + pressControlShiftT(); + + // Channel 2 should now be open again + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + } - // // Channel 2 should now be open again - // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - // AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - // } // [Test] // public void TestCtrlShiftTShortcut2() // { @@ -247,31 +248,30 @@ namespace osu.Game.Tests.Visual.Online // channelManager.JoinChannel(channel1); // channelManager.JoinChannel(channel2); // }); - + // // // Close channels 1 and channel 2 // AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); // AddStep("Leave channel 1", () => channelManager.LeaveChannel(channel1)); // AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); - + // // pressControlShiftT(); // // Channel 1 should now be open again. Channel 2 should not // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); // AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); - + // // pressControlShiftT(); - + // // // Both Channel 1 and Channel 2 should be open // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); // AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - + // // // Processing this again should have not effect // pressControlShiftT(); // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); // AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - + // // // Close channel 2 again // AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); - // // Both channel 1 and channel 2 should be open // pressControlShiftT(); // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); From 5dc917c1081c52a7bf2978d69eb11fecd0022604 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Mon, 14 Dec 2020 22:30:16 -0500 Subject: [PATCH 0136/1621] Modified test case I believe the tests are failing because the shortcut only works when you select a channel on the channel dropdown. So I updated the testcases to do this. --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index ce590839c2..2db6bf6220 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -233,6 +233,8 @@ namespace osu.Game.Tests.Visual.Online AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + pressControlShiftT(); // Channel 2 should now be open again From c000d6d622512eead83e310ae9e0a116a4f9b288 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Mon, 14 Dec 2020 23:30:57 -0500 Subject: [PATCH 0137/1621] Minor formatting change Still trying to fix the CI problems --- .../Visual/Online/TestSceneChatOverlay.cs | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 2db6bf6220..15fb12df6a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -242,43 +242,6 @@ namespace osu.Game.Tests.Visual.Online AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); } - // [Test] - // public void TestCtrlShiftTShortcut2() - // { - // AddStep("Join 2 channels", () => - // { - // channelManager.JoinChannel(channel1); - // channelManager.JoinChannel(channel2); - // }); - // - // // Close channels 1 and channel 2 - // AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); - // AddStep("Leave channel 1", () => channelManager.LeaveChannel(channel1)); - // AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); - // - // pressControlShiftT(); - // // Channel 1 should now be open again. Channel 2 should not - // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - // AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); - // - // pressControlShiftT(); - // - // // Both Channel 1 and Channel 2 should be open - // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - // AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - // - // // Processing this again should have not effect - // pressControlShiftT(); - // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - // AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - // - // // Close channel 2 again - // AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); - // // Both channel 1 and channel 2 should be open - // pressControlShiftT(); - // AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - // AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - // } private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From 16a103952282b574cf2893cf9c028f31c1ee8e55 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Tue, 15 Dec 2020 00:00:43 -0500 Subject: [PATCH 0138/1621] Completely removed all CI tests THe GitHub CI is still failing for unknown reasons. I tried to remove them all to see what the problem really is --- .../Visual/Online/TestSceneChatOverlay.cs | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 15fb12df6a..fca642ad6c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -208,40 +208,6 @@ namespace osu.Game.Tests.Visual.Online AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } - // Control-Shift-T should reopen the most recently closed tab that has not - // already been reopened - private void pressControlShiftT() - { - AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); - AddStep("Press and hold Shift", () => InputManager.PressKey(Key.ShiftLeft)); - AddStep("Press T", () => InputManager.Key(Key.T)); - AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); - AddStep("Release Shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); - } - - [Test] - public void TestCtrlShiftTShortcut1() - { - AddStep("Join 2 channels", () => - { - channelManager.JoinChannel(channel1); - channelManager.JoinChannel(channel2); - }); - - // Close channel 2 - AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); - - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - - pressControlShiftT(); - - // Channel 2 should now be open again - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - } - private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From bd32dbdc2f6a9491ba931356140799d687ad3b8a Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Tue, 15 Dec 2020 00:10:11 -0500 Subject: [PATCH 0139/1621] Added back a modified test case Added a simple test for Ctrl+Shift+T. --- .../Visual/Online/TestSceneChatOverlay.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index fca642ad6c..8795e50645 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -208,6 +208,41 @@ namespace osu.Game.Tests.Visual.Online AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } + // Control-Shift-T should reopen the most recently closed tab that has not + // already been reopened + private void pressControlShiftT() + { + AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); + AddStep("Press and hold Shift", () => InputManager.PressKey(Key.ShiftLeft)); + AddStep("Press T", () => InputManager.Key(Key.T)); + AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); + AddStep("Release Shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + } + + [Test] + public void TestCtrlShiftTShortcut1() + { + AddStep("Join 2 channels", () => + { + channelManager.JoinChannel(channel1); + channelManager.JoinChannel(channel2); + }); + + // Close channel 2 + AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); + AddStep("Close channel 2", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + pressControlShiftT(); + + // Channel 2 should now be open again + AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); + } + private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From 1b702b2b1f0341f1336351f8ebded3874caa1030 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Tue, 15 Dec 2020 11:57:51 -0500 Subject: [PATCH 0140/1621] Removed all CI tests Just trying to get a commit that passes all CI tests --- .../Visual/Online/TestSceneChatOverlay.cs | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 8795e50645..fca642ad6c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -208,41 +208,6 @@ namespace osu.Game.Tests.Visual.Online AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } - // Control-Shift-T should reopen the most recently closed tab that has not - // already been reopened - private void pressControlShiftT() - { - AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); - AddStep("Press and hold Shift", () => InputManager.PressKey(Key.ShiftLeft)); - AddStep("Press T", () => InputManager.Key(Key.T)); - AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); - AddStep("Release Shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); - } - - [Test] - public void TestCtrlShiftTShortcut1() - { - AddStep("Join 2 channels", () => - { - channelManager.JoinChannel(channel1); - channelManager.JoinChannel(channel2); - }); - - // Close channel 2 - AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); - AddStep("Close channel 2", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); - - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - - pressControlShiftT(); - - // Channel 2 should now be open again - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 open", () => channelManager.JoinedChannels.Contains(channel2)); - } - private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From 8bd787dce976fb4564d5ebb26f283f1cbfa67e86 Mon Sep 17 00:00:00 2001 From: Jess Meng Date: Tue, 15 Dec 2020 20:50:23 -0800 Subject: [PATCH 0141/1621] Moved private methods beneath public ones --- .../Visual/Online/TestSceneChatOverlay.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 68fc8ac8c0..9d0be85a3a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -208,13 +208,6 @@ namespace osu.Game.Tests.Visual.Online AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } - private void pressControlW() - { - AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); - AddStep("Press W", () => InputManager.Key(Key.W)); - AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); - } - [Test] public void TestCtrlWShortcut() { @@ -240,13 +233,6 @@ namespace osu.Game.Tests.Visual.Online AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } - private void pressControlT() - { - AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); - AddStep("Press T", () => InputManager.Key(Key.T)); - AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); - } - [Test] public void TestCtrlTShortcut() { @@ -270,6 +256,20 @@ namespace osu.Game.Tests.Visual.Online InputManager.ReleaseKey(Key.AltLeft); } + private void pressControlW() + { + AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); + AddStep("Press W", () => InputManager.Key(Key.W)); + AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); + } + + private void pressControlT() + { + AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); + AddStep("Press T", () => InputManager.Key(Key.T)); + AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); + } + private void clickDrawable(Drawable d) { InputManager.MoveMouseTo(d); From 7b169c4f62428c69fbaee874eb22ca278bb72f78 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Wed, 16 Dec 2020 10:13:50 -0500 Subject: [PATCH 0142/1621] Changed internal closedChannel list to store channels names instead of channel objects This is for efficiency purposes; it's more efficient to just store the names than store the whole object. --- osu.Game/Online/Chat/ChannelManager.cs | 27 ++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 607c9a1223..303696c294 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -33,8 +33,8 @@ namespace osu.Game.Online.Chat private readonly BindableList availableChannels = new BindableList(); private readonly BindableList joinedChannels = new BindableList(); - // Keeps a list of closed channels. More recently closed channels appear at higher indeces - private readonly BindableList closedChannels = new BindableList(); + // Keeps a list of closed channel identifiers + private readonly BindableList closedChannels = new BindableList(); // For efficiency purposes, this constant bounds the number of closed channels we store. // This number is somewhat arbitrary; future developers are free to modify it. @@ -424,7 +424,7 @@ namespace osu.Game.Online.Chat } // insert at the end of the closedChannels list - closedChannels.Insert(closedChannels.Count, channel); + closedChannels.Insert(closedChannels.Count, channel.Name); if (channel.Joined.Value) { @@ -445,13 +445,24 @@ namespace osu.Game.Online.Chat return; } - Channel lastClosedChannel = closedChannels.Last(); + string lastClosedChannelName = closedChannels.Last(); + closedChannels.RemoveAt(closedChannels.Count - 1); - closedChannels.Remove(lastClosedChannel); + // types did not work with existing enumerable interfaces funcitons like Contains + for (int i = 0; i < joinedChannels.Count; i++) + { + // If the user already joined the channel, try the next + // channel in the list + if (joinedChannels[i].Name == lastClosedChannelName) + { + JoinLastClosedChannel(); + return; + } + } - // If the user already joined the channel, try the next - // channel in the list - if (joinedChannels.IndexOf(lastClosedChannel) >= 0) + Channel lastClosedChannel = AvailableChannels.FirstOrDefault(c => c.Name == lastClosedChannelName); + + if (lastClosedChannel == null) { JoinLastClosedChannel(); } From 2f8a085adfb89ce696f6eb1212a63d9287da46fc Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Wed, 16 Dec 2020 12:04:07 -0500 Subject: [PATCH 0143/1621] Reworked reopening last tab to no longer use recursion A reviewer of the pull request was concerned about recursion. I changed the code to be iterative. --- osu.Game/Online/Chat/ChannelManager.cs | 56 ++++++++++++++++---------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 303696c294..1bbd2c3471 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -445,33 +445,47 @@ namespace osu.Game.Online.Chat return; } - string lastClosedChannelName = closedChannels.Last(); - closedChannels.RemoveAt(closedChannels.Count - 1); - - // types did not work with existing enumerable interfaces funcitons like Contains - for (int i = 0; i < joinedChannels.Count; i++) + // This nested loop could be eliminated if a check was added so that + // when the code opens a channel it removes from the closedChannel list. + // However, this would require adding an O(|closeChannels|) work operation + // every time the user joins a channel, which would make joining a channel + // slower. I wanted to centralize all major slowdowns so they + // can only occur if the user actually decides to use this feature. + for (int i = closedChannels.Count - 1; i >= 0; i--) { - // If the user already joined the channel, try the next - // channel in the list - if (joinedChannels[i].Name == lastClosedChannelName) + string lastClosedChannelName = closedChannels.Last(); + closedChannels.RemoveAt(closedChannels.Count - 1); + bool alreadyJoined = false; + // If the user already joined the channel, do not + // try to join it + for (int j = 0; j < joinedChannels.Count; j++) { - JoinLastClosedChannel(); - return; + if (joinedChannels[j].Name == lastClosedChannelName) + { + alreadyJoined = true; + break; + } + } + + if (alreadyJoined == false) + { + Channel lastClosedChannel = AvailableChannels.FirstOrDefault(c => c.Name == lastClosedChannelName); + + if (lastClosedChannel == null) + { + continue; + } + else + { + JoinChannel(lastClosedChannel); + return; + } } } - - Channel lastClosedChannel = AvailableChannels.FirstOrDefault(c => c.Name == lastClosedChannelName); - - if (lastClosedChannel == null) - { - JoinLastClosedChannel(); - } - else - { - JoinChannel(lastClosedChannel); - } } + + private long lastMessageId; private bool channelsInitialised; From b37a983fbf6397d2acc8a244dd408f62b8305f18 Mon Sep 17 00:00:00 2001 From: Joseph-Ramos-CMU Date: Wed, 16 Dec 2020 12:56:36 -0500 Subject: [PATCH 0144/1621] Formatting fixes Fixed some formatting issues to comply with InspectCode. --- osu.Game/Online/Chat/ChannelManager.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 1bbd2c3471..51bbe6ca45 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -447,7 +447,7 @@ namespace osu.Game.Online.Chat // This nested loop could be eliminated if a check was added so that // when the code opens a channel it removes from the closedChannel list. - // However, this would require adding an O(|closeChannels|) work operation + // However, this would require adding an O(|closeChannels|) work operation // every time the user joins a channel, which would make joining a channel // slower. I wanted to centralize all major slowdowns so they // can only occur if the user actually decides to use this feature. @@ -456,6 +456,7 @@ namespace osu.Game.Online.Chat string lastClosedChannelName = closedChannels.Last(); closedChannels.RemoveAt(closedChannels.Count - 1); bool alreadyJoined = false; + // If the user already joined the channel, do not // try to join it for (int j = 0; j < joinedChannels.Count; j++) @@ -471,11 +472,7 @@ namespace osu.Game.Online.Chat { Channel lastClosedChannel = AvailableChannels.FirstOrDefault(c => c.Name == lastClosedChannelName); - if (lastClosedChannel == null) - { - continue; - } - else + if (lastClosedChannel != null) { JoinChannel(lastClosedChannel); return; @@ -484,8 +481,6 @@ namespace osu.Game.Online.Chat } } - - private long lastMessageId; private bool channelsInitialised; From 71a082110abe74015dfbaf62b7e6b1dca7b2a726 Mon Sep 17 00:00:00 2001 From: Angela Zhang Date: Thu, 17 Dec 2020 16:56:34 -0600 Subject: [PATCH 0145/1621] Making style changes + supports reopening PM chats --- osu.Game/Online/Chat/ChannelManager.cs | 54 ++++++++++++++------------ 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 51bbe6ca45..54ef3c6c30 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -33,8 +33,12 @@ namespace osu.Game.Online.Chat private readonly BindableList availableChannels = new BindableList(); private readonly BindableList joinedChannels = new BindableList(); - // Keeps a list of closed channel identifiers - private readonly BindableList closedChannels = new BindableList(); + + /// + /// Keeps a list of closed channel identifiers. Stores the channel ID for normal + /// channels, or the user ID for PM channels + /// + private readonly BindableList closedChannelIds = new BindableList(); // For efficiency purposes, this constant bounds the number of closed channels we store. // This number is somewhat arbitrary; future developers are free to modify it. @@ -418,13 +422,13 @@ namespace osu.Game.Online.Chat // Prevent the closedChannel list from exceeding the max size // by removing the oldest element - if (closedChannels.Count >= closed_channels_max_size) + if (closedChannelIds.Count >= closed_channels_max_size) { - closedChannels.RemoveAt(0); + closedChannelIds.RemoveAt(0); } - // insert at the end of the closedChannels list - closedChannels.Insert(closedChannels.Count, channel.Name); + // For PM channels, we store the user ID; else, we store the channel id + closedChannelIds.Add(channel.Type == ChannelType.PM ? channel.Users.First().Id : channel.Id); if (channel.Joined.Value) { @@ -440,7 +444,7 @@ namespace osu.Game.Online.Chat /// public void JoinLastClosedChannel() { - if (closedChannels.Count <= 0) + if (closedChannelIds.Count <= 0) { return; } @@ -451,32 +455,32 @@ namespace osu.Game.Online.Chat // every time the user joins a channel, which would make joining a channel // slower. I wanted to centralize all major slowdowns so they // can only occur if the user actually decides to use this feature. - for (int i = closedChannels.Count - 1; i >= 0; i--) + while (closedChannelIds.Count != 0) { - string lastClosedChannelName = closedChannels.Last(); - closedChannels.RemoveAt(closedChannels.Count - 1); - bool alreadyJoined = false; + long lastClosedChannelId = closedChannelIds.Last(); + closedChannelIds.RemoveAt(closedChannelIds.Count - 1); - // If the user already joined the channel, do not - // try to join it - for (int j = 0; j < joinedChannels.Count; j++) - { - if (joinedChannels[j].Name == lastClosedChannelName) - { - alreadyJoined = true; - break; - } - } + bool lookupCondition(Channel ch) => + ch.Type == ChannelType.PM ? ch.Users.Any(u => u.Id == lastClosedChannelId) + : ch.Id == lastClosedChannelId; - if (alreadyJoined == false) + // If the user hasn't already joined the channel, try to join it + if (joinedChannels.FirstOrDefault(lookupCondition) == null) { - Channel lastClosedChannel = AvailableChannels.FirstOrDefault(c => c.Name == lastClosedChannelName); + Channel lastClosedChannel = AvailableChannels.FirstOrDefault(lookupCondition); if (lastClosedChannel != null) { - JoinChannel(lastClosedChannel); - return; + CurrentChannel.Value = JoinChannel(lastClosedChannel); } + else + { + // Try to get User to open PM chat + var req = new GetUserRequest(lastClosedChannelId); + req.Success += user => CurrentChannel.Value = JoinChannel(new Channel(user)); + api.Queue(req); + } + return; } } } From cbfa292c05d0d67e57c60066cc3181a692c4d6ef Mon Sep 17 00:00:00 2001 From: Angela Zhang Date: Thu, 17 Dec 2020 16:59:19 -0600 Subject: [PATCH 0146/1621] Minor test style fixes --- .../Visual/Online/TestSceneChatOverlay.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 9d0be85a3a..12a24963c4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -220,15 +220,15 @@ namespace osu.Game.Tests.Visual.Online // Want to close channel 2 AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); - pressControlW(); + AddStep("Press Ctrl + W", () => pressControlW()); // Channel 2 should be closed - AddAssert("channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); + AddAssert("Channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); + AddAssert("Channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); // Want to close channel 1 AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - pressControlW(); + AddStep("Press Ctrl + W", () => pressControlW()); // Channel 1 and channel 2 should be closed AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } @@ -243,7 +243,7 @@ namespace osu.Game.Tests.Visual.Online }); // Want to join another channel - pressControlT(); + AddStep("Press Ctrl + T", () => pressControlT()); // Selector should be visible AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); } @@ -258,16 +258,16 @@ namespace osu.Game.Tests.Visual.Online private void pressControlW() { - AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); - AddStep("Press W", () => InputManager.Key(Key.W)); - AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.W); + InputManager.ReleaseKey(Key.ControlLeft); } private void pressControlT() { - AddStep("Press and hold Control", () => InputManager.PressKey(Key.ControlLeft)); - AddStep("Press T", () => InputManager.Key(Key.T)); - AddStep("Release Control", () => InputManager.ReleaseKey(Key.ControlLeft)); + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.T); + InputManager.ReleaseKey(Key.ControlLeft); } private void clickDrawable(Drawable d) From 454c7538c0dcc38fc823ae1d59d43475d9ccd8c9 Mon Sep 17 00:00:00 2001 From: Angela Zhang Date: Thu, 17 Dec 2020 17:59:36 -0600 Subject: [PATCH 0147/1621] CI Style Fixes --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 7 ++++--- osu.Game/Online/Chat/ChannelManager.cs | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 12a24963c4..52dafbcfb2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -219,8 +219,8 @@ namespace osu.Game.Tests.Visual.Online // Want to close channel 2 AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); + AddStep("Press Ctrl + W", pressControlW); - AddStep("Press Ctrl + W", () => pressControlW()); // Channel 2 should be closed AddAssert("Channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); AddAssert("Channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); @@ -228,7 +228,7 @@ namespace osu.Game.Tests.Visual.Online // Want to close channel 1 AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - AddStep("Press Ctrl + W", () => pressControlW()); + AddStep("Press Ctrl + W", pressControlW); // Channel 1 and channel 2 should be closed AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } @@ -243,7 +243,8 @@ namespace osu.Game.Tests.Visual.Online }); // Want to join another channel - AddStep("Press Ctrl + T", () => pressControlT()); + AddStep("Press Ctrl + T", pressControlT); + // Selector should be visible AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); } diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 54ef3c6c30..5936c4e580 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -33,7 +33,6 @@ namespace osu.Game.Online.Chat private readonly BindableList availableChannels = new BindableList(); private readonly BindableList joinedChannels = new BindableList(); - /// /// Keeps a list of closed channel identifiers. Stores the channel ID for normal /// channels, or the user ID for PM channels @@ -461,8 +460,9 @@ namespace osu.Game.Online.Chat closedChannelIds.RemoveAt(closedChannelIds.Count - 1); bool lookupCondition(Channel ch) => - ch.Type == ChannelType.PM ? ch.Users.Any(u => u.Id == lastClosedChannelId) - : ch.Id == lastClosedChannelId; + ch.Type == ChannelType.PM + ? ch.Users.Any(u => u.Id == lastClosedChannelId) + : ch.Id == lastClosedChannelId; // If the user hasn't already joined the channel, try to join it if (joinedChannels.FirstOrDefault(lookupCondition) == null) @@ -480,6 +480,7 @@ namespace osu.Game.Online.Chat req.Success += user => CurrentChannel.Value = JoinChannel(new Channel(user)); api.Queue(req); } + return; } } From 45482e8709d50527aa9a292cb0c0599390bff50d Mon Sep 17 00:00:00 2001 From: Angela Zhang Date: Thu, 17 Dec 2020 18:43:39 -0600 Subject: [PATCH 0148/1621] Whitespace fixes --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 52dafbcfb2..8cb2d3df95 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -244,7 +244,7 @@ namespace osu.Game.Tests.Visual.Online // Want to join another channel AddStep("Press Ctrl + T", pressControlT); - + // Selector should be visible AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); } diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 5936c4e580..164330e78b 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -459,10 +459,9 @@ namespace osu.Game.Online.Chat long lastClosedChannelId = closedChannelIds.Last(); closedChannelIds.RemoveAt(closedChannelIds.Count - 1); - bool lookupCondition(Channel ch) => - ch.Type == ChannelType.PM - ? ch.Users.Any(u => u.Id == lastClosedChannelId) - : ch.Id == lastClosedChannelId; + bool lookupCondition(Channel ch) => ch.Type == ChannelType.PM + ? ch.Users.Any(u => u.Id == lastClosedChannelId) + : ch.Id == lastClosedChannelId; // If the user hasn't already joined the channel, try to join it if (joinedChannels.FirstOrDefault(lookupCondition) == null) @@ -480,7 +479,7 @@ namespace osu.Game.Online.Chat req.Success += user => CurrentChannel.Value = JoinChannel(new Channel(user)); api.Queue(req); } - + return; } } From 7d326c7f2462d72f1d87d7e1d4e6ddf90fc0cafc Mon Sep 17 00:00:00 2001 From: Angela Zhang Date: Sun, 20 Dec 2020 13:18:00 -0600 Subject: [PATCH 0149/1621] Review changes + added tests --- .../Visual/Online/TestSceneChatOverlay.cs | 58 ++++++++++++- osu.Game/Online/Chat/ChannelManager.cs | 81 ++++++++++++------- 2 files changed, 110 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 8cb2d3df95..beb069d2a2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -34,6 +34,7 @@ namespace osu.Game.Tests.Visual.Online private Channel previousChannel => joinedChannels.ElementAt(joinedChannels.ToList().IndexOf(currentChannel) - 1); private Channel channel1 => channels[0]; private Channel channel2 => channels[1]; + private Channel channel3 => channels[2]; public TestSceneChatOverlay() { @@ -42,7 +43,8 @@ namespace osu.Game.Tests.Visual.Online { Name = $"Channel no. {index}", Topic = index == 3 ? null : $"We talk about the number {index} here", - Type = index % 2 == 0 ? ChannelType.PM : ChannelType.Temporary + Type = index % 2 == 0 ? ChannelType.PM : ChannelType.Temporary, + Id = index }) .ToList(); } @@ -249,6 +251,51 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); } + [Test] + public void TestCtrlShiftTShortcut() + { + AddStep("Join 3 channels", () => + { + channelManager.JoinChannel(channel1); + channelManager.JoinChannel(channel2); + channelManager.JoinChannel(channel3); + }); + + // Should do nothing + AddStep("Press Ctrl + Shift + T", pressControlShiftT); + AddAssert("All channels still open", () => channelManager.JoinedChannels.Count == 3); + + // Close channel 1 + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + AddStep("Click normal close button", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); + AddAssert("Channel 1 closed", () => !channelManager.JoinedChannels.Contains(channel1)); + AddAssert("Other channels still open", () => channelManager.JoinedChannels.Count == 2); + + // Reopen channel 1 + AddStep("Press Ctrl + Shift + T", pressControlShiftT); + AddAssert("All channels now open", () => channelManager.JoinedChannels.Count == 3); + AddAssert("Current channel is channel 1", () => currentChannel == channel1); + + // Close two channels + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + AddStep("Close channel 1", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); + AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); + AddStep("Close channel 2", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); + AddAssert("Only one channel open", () => channelManager.JoinedChannels.Count == 1); + AddAssert("Current channel is channel 3", () => currentChannel == channel3); + + // Should first re-open channel 2 + AddStep("Press Ctrl + Shift + T", pressControlShiftT); + AddAssert("Channel 1 still closed", () => !channelManager.JoinedChannels.Contains(channel1)); + AddAssert("Channel 2 now open", () => channelManager.JoinedChannels.Contains(channel2)); + AddAssert("Current channel is channel 2", () => currentChannel == channel2); + + // Should then re-open channel 1 + AddStep("Press Ctrl + Shift + T", pressControlShiftT); + AddAssert("All channels now open", () => channelManager.JoinedChannels.Count == 3); + AddAssert("Current channel is channel 1", () => currentChannel == channel1); + } + private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; @@ -271,6 +318,15 @@ namespace osu.Game.Tests.Visual.Online InputManager.ReleaseKey(Key.ControlLeft); } + private void pressControlShiftT() + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.T); + InputManager.ReleaseKey(Key.ShiftLeft); + InputManager.ReleaseKey(Key.ControlLeft); + } + private void clickDrawable(Drawable d) { InputManager.MoveMouseTo(d); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 164330e78b..ecf5c6e245 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Chat.Tabs; @@ -34,10 +35,9 @@ namespace osu.Game.Online.Chat private readonly BindableList joinedChannels = new BindableList(); /// - /// Keeps a list of closed channel identifiers. Stores the channel ID for normal - /// channels, or the user ID for PM channels + /// Keeps a stack of recently closed channels /// - private readonly BindableList closedChannelIds = new BindableList(); + private readonly BindableList closedChannels = new BindableList(); // For efficiency purposes, this constant bounds the number of closed channels we store. // This number is somewhat arbitrary; future developers are free to modify it. @@ -62,6 +62,10 @@ namespace osu.Game.Online.Chat [Resolved] private IAPIProvider api { get; set; } + + [Resolved] + private UserLookupCache users { get; set; } + public readonly BindableBool HighPollRate = new BindableBool(); public ChannelManager() @@ -421,13 +425,15 @@ namespace osu.Game.Online.Chat // Prevent the closedChannel list from exceeding the max size // by removing the oldest element - if (closedChannelIds.Count >= closed_channels_max_size) + if (closedChannels.Count >= closed_channels_max_size) { - closedChannelIds.RemoveAt(0); + closedChannels.RemoveAt(0); } // For PM channels, we store the user ID; else, we store the channel id - closedChannelIds.Add(channel.Type == ChannelType.PM ? channel.Users.First().Id : channel.Id); + closedChannels.Add(channel.Type == ChannelType.PM + ? new ClosedChannel(ChannelType.PM, channel.Users.Single().Id) + : new ClosedChannel(channel.Type, channel.Id)); if (channel.Joined.Value) { @@ -443,41 +449,36 @@ namespace osu.Game.Online.Chat /// public void JoinLastClosedChannel() { - if (closedChannelIds.Count <= 0) - { - return; - } - - // This nested loop could be eliminated if a check was added so that + // This loop could be eliminated if a check was added so that // when the code opens a channel it removes from the closedChannel list. // However, this would require adding an O(|closeChannels|) work operation // every time the user joins a channel, which would make joining a channel - // slower. I wanted to centralize all major slowdowns so they + // slower. We wanted to centralize all major slowdowns so they // can only occur if the user actually decides to use this feature. - while (closedChannelIds.Count != 0) + while (closedChannels.Count > 0) { - long lastClosedChannelId = closedChannelIds.Last(); - closedChannelIds.RemoveAt(closedChannelIds.Count - 1); - - bool lookupCondition(Channel ch) => ch.Type == ChannelType.PM - ? ch.Users.Any(u => u.Id == lastClosedChannelId) - : ch.Id == lastClosedChannelId; + ClosedChannel lastClosedChannel = closedChannels.Last(); + closedChannels.RemoveAt(closedChannels.Count - 1); // If the user hasn't already joined the channel, try to join it - if (joinedChannels.FirstOrDefault(lookupCondition) == null) + if (joinedChannels.FirstOrDefault(lastClosedChannel.IsEqual) == null) { - Channel lastClosedChannel = AvailableChannels.FirstOrDefault(lookupCondition); + Channel lastChannel = AvailableChannels.FirstOrDefault(lastClosedChannel.IsEqual); - if (lastClosedChannel != null) + if (lastChannel != null) { - CurrentChannel.Value = JoinChannel(lastClosedChannel); + // Channel exists as an availaable channel, directly join it + CurrentChannel.Value = JoinChannel(lastChannel); } - else + else if (lastClosedChannel.Type == ChannelType.PM) { // Try to get User to open PM chat - var req = new GetUserRequest(lastClosedChannelId); - req.Success += user => CurrentChannel.Value = JoinChannel(new Channel(user)); - api.Queue(req); + users.GetUserAsync((int)lastClosedChannel.Id).ContinueWith(u => + { + if (u.Result == null) return; + + Schedule(() => CurrentChannel.Value = JoinChannel(new Channel(u.Result))); + }); } return; @@ -569,4 +570,28 @@ namespace osu.Game.Online.Chat { } } + + /// + /// Class that stores information about a closed channel + /// + public class ClosedChannel + { + public ChannelType Type; + public long Id; + + public ClosedChannel(ChannelType type, long id) + { + Type = type; + Id = id; + } + + public bool IsEqual(Channel channel) + { + if (channel.Type != this.Type) return false; + + return this.Type == ChannelType.PM + ? channel.Users.Single().Id == this.Id + : channel.Id == this.Id; + } + } } From 74bd2f41e60b35553f2bad451c1c225a224eebea Mon Sep 17 00:00:00 2001 From: Angela Zhang Date: Sun, 20 Dec 2020 13:51:39 -0600 Subject: [PATCH 0150/1621] Style fixes --- osu.Game/Online/Chat/ChannelManager.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index ecf5c6e245..f4d1aeae84 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -62,7 +62,6 @@ namespace osu.Game.Online.Chat [Resolved] private IAPIProvider api { get; set; } - [Resolved] private UserLookupCache users { get; set; } @@ -587,11 +586,11 @@ namespace osu.Game.Online.Chat public bool IsEqual(Channel channel) { - if (channel.Type != this.Type) return false; + if (channel.Type != Type) return false; - return this.Type == ChannelType.PM - ? channel.Users.Single().Id == this.Id - : channel.Id == this.Id; + return Type == ChannelType.PM + ? channel.Users.Single().Id == Id + : channel.Id == Id; } } } From ee103c9a1e4804744bed343d7a647f2d8e6a023f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jan 2021 16:59:01 +0900 Subject: [PATCH 0151/1621] 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 0152/1621] 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 0153/1621] 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 0154/1621] 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 0155/1621] 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 0156/1621] 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 0157/1621] 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 0158/1621] 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 0159/1621] 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 0160/1621] 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 0161/1621] 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 0162/1621] 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 0163/1621] 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 0164/1621] 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 0165/1621] 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 0166/1621] 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 0167/1621] 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 0168/1621] 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 0169/1621] 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 0170/1621] 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 0171/1621] 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 0172/1621] 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 0173/1621] 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 0174/1621] 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 0175/1621] 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 0176/1621] 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 0177/1621] 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 0178/1621] 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 0179/1621] 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 0180/1621] 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 0181/1621] 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 0182/1621] 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 0183/1621] 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 0184/1621] 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 0185/1621] 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 0186/1621] 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 0187/1621] 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 0188/1621] 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 0189/1621] 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 0190/1621] 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 0191/1621] 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 0192/1621] 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 0193/1621] 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 0194/1621] 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 0195/1621] 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 0196/1621] 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 0197/1621] 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 0198/1621] 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 0199/1621] 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 0200/1621] 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 0201/1621] 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 0202/1621] 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 0203/1621] 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 0204/1621] 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 0205/1621] 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 0206/1621] 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 0207/1621] 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 0208/1621] 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 0209/1621] 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 0210/1621] 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 0211/1621] 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 0212/1621] 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 0213/1621] 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 0214/1621] 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 0215/1621] 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 0216/1621] 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 0217/1621] 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 0218/1621] 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 0219/1621] 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 0220/1621] 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 0221/1621] 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 0222/1621] 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 0223/1621] 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 0224/1621] 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 0225/1621] 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 0226/1621] 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 0227/1621] 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 0228/1621] 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 0229/1621] 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 323e4ac26b8172865bc6fbede9c39fb05ee5da40 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sun, 21 Feb 2021 18:24:27 +1100 Subject: [PATCH 0230/1621] Refactor catch Movement skill to not require explicit clockrate usage In catch, rate adjustment mods do not only affect the timings of hitobjects, but also the speed of the player's catcher. This catcher speed change has an impact on difficulty which is currently accounted for by using the clockrate directly in calculations. Semantically this is a bad idea because clockrate adjustments are supposed to be fully accounted for in DifficultyHitObjects, but passing clockrate here for the purpose of being used as catcher speed doesn't make much sense, especially since it is copied in every DifficultyHitObject despite being the same value. It makes more sense to account for this catch specific impact by handling rate adjustment mods in a catch specific way, or more specifically in a Movement skill specific way. --- .../Preprocessing/CatchDifficultyHitObject.cs | 3 --- .../Difficulty/Skills/Movement.cs | 15 +++++++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index d936ef97ac..e19098c580 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -24,8 +24,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing /// public readonly double StrainTime; - public readonly double ClockRate; - public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth) : base(hitObject, lastObject, clockRate) { @@ -37,7 +35,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure StrainTime = Math.Max(40, DeltaTime); - ClockRate = clockRate; } } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 9ad719be1a..7d61be7bb1 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; @@ -26,10 +27,20 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills private float lastDistanceMoved; private double lastStrainTime; + /// + /// The speed multiplier applied to the player's catcher. + /// + private readonly double catcherSpeedMultiplier; + public Movement(Mod[] mods, float halfCatcherWidth) : base(mods) { HalfCatcherWidth = halfCatcherWidth; + + // In catch, rate adjustment mods do not only affect the timings of hitobjects, + // but also the speed of the player's catcher, which has an impact on difficulty + var rateAdjustMod = mods.FirstOrDefault(m => m is ModRateAdjust); + catcherSpeedMultiplier = (rateAdjustMod as ModRateAdjust)?.SpeedChange.Value ?? 1; } protected override double StrainValueOf(DifficultyHitObject current) @@ -46,7 +57,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills float distanceMoved = playerPosition - lastPlayerPosition.Value; - double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catchCurrent.ClockRate); + double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catcherSpeedMultiplier); double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510); double sqrtStrain = Math.Sqrt(weightedStrainTime); @@ -79,7 +90,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills playerPosition = catchCurrent.NormalizedPosition; } - distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values + distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catcherSpeedMultiplier, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values } lastPlayerPosition = playerPosition; From 4d976094d1c69613ef581828d638ffdb04a48984 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Mar 2021 20:07:53 +0900 Subject: [PATCH 0231/1621] 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 34429a02e7fac260f7c247d5b28b6744cefff5ec Mon Sep 17 00:00:00 2001 From: ilsubyeega Date: Sun, 28 Mar 2021 03:13:05 +0900 Subject: [PATCH 0232/1621] Use avatar_url from user first instead of a.ppy.sh in DrawableAvatar --- osu.Game/Users/Drawables/DrawableAvatar.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs index 3dae3afe3f..15ef7d2df0 100644 --- a/osu.Game/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Online.API; namespace osu.Game.Users.Drawables { @@ -13,6 +14,9 @@ namespace osu.Game.Users.Drawables { private readonly User user; + [Resolved(CanBeNull=true)] + private IAPIProvider api { get; set; } + /// /// A simple, non-interactable avatar sprite for the specified user. /// @@ -30,9 +34,11 @@ namespace osu.Game.Users.Drawables [BackgroundDependencyLoader] private void load(LargeTextureStore textures) { - if (user != null && user.Id > 1) + if (api != null && user?.AvatarUrl != null) + Texture = textures.Get(user.AvatarUrl.StartsWith('/') ? $"{api.WebsiteRootUrl}{user.AvatarUrl}" : user.AvatarUrl); + else if (user != null && user.Id > 1) Texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); - + Texture ??= textures.Get(@"Online/avatar-guest"); } From be08460bea4c10391dd980b2d437a5ee4364e248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Mar 2021 19:59:33 +0100 Subject: [PATCH 0233/1621] Fix formatting issues --- osu.Game/Users/Drawables/DrawableAvatar.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs index 15ef7d2df0..079d4a932c 100644 --- a/osu.Game/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -14,7 +14,7 @@ namespace osu.Game.Users.Drawables { private readonly User user; - [Resolved(CanBeNull=true)] + [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } /// @@ -38,7 +38,7 @@ namespace osu.Game.Users.Drawables Texture = textures.Get(user.AvatarUrl.StartsWith('/') ? $"{api.WebsiteRootUrl}{user.AvatarUrl}" : user.AvatarUrl); else if (user != null && user.Id > 1) Texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); - + Texture ??= textures.Get(@"Online/avatar-guest"); } From 32df02084db91ecb7ab419a04df4f60d61f30ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Mar 2021 20:00:49 +0100 Subject: [PATCH 0234/1621] Extract variable to avoid redundant accesses --- osu.Game/Users/Drawables/DrawableAvatar.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs index 079d4a932c..c672c6fa1d 100644 --- a/osu.Game/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -34,8 +34,9 @@ namespace osu.Game.Users.Drawables [BackgroundDependencyLoader] private void load(LargeTextureStore textures) { - if (api != null && user?.AvatarUrl != null) - Texture = textures.Get(user.AvatarUrl.StartsWith('/') ? $"{api.WebsiteRootUrl}{user.AvatarUrl}" : user.AvatarUrl); + string avatarUrl = user?.AvatarUrl; + if (api != null && avatarUrl != null) + Texture = textures.Get(avatarUrl.StartsWith('/') ? $"{api.WebsiteRootUrl}{avatarUrl}" : avatarUrl); else if (user != null && user.Id > 1) Texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); From b3d3c7ecacf7762c22f5c2f556164c134ce9e8cc Mon Sep 17 00:00:00 2001 From: ilsubyeega Date: Mon, 29 Mar 2021 20:01:20 +0900 Subject: [PATCH 0235/1621] Revert relative url checking to AvatarUrl --- osu.Game/Users/Drawables/DrawableAvatar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs index c672c6fa1d..98fc10d7ed 100644 --- a/osu.Game/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -36,7 +36,7 @@ namespace osu.Game.Users.Drawables { string avatarUrl = user?.AvatarUrl; if (api != null && avatarUrl != null) - Texture = textures.Get(avatarUrl.StartsWith('/') ? $"{api.WebsiteRootUrl}{avatarUrl}" : avatarUrl); + Texture = textures.Get(avatarUrl); else if (user != null && user.Id > 1) Texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); From dc3af1e0f7634b373c43957188ec1bfec7f0a3f3 Mon Sep 17 00:00:00 2001 From: ilsubyeega Date: Mon, 29 Mar 2021 20:05:24 +0900 Subject: [PATCH 0236/1621] Remove IAPIProvider since its not required at DrawableAvatar --- osu.Game/Users/Drawables/DrawableAvatar.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs index 98fc10d7ed..930f1c07a1 100644 --- a/osu.Game/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Game.Online.API; namespace osu.Game.Users.Drawables { @@ -14,9 +13,6 @@ namespace osu.Game.Users.Drawables { private readonly User user; - [Resolved(CanBeNull = true)] - private IAPIProvider api { get; set; } - /// /// A simple, non-interactable avatar sprite for the specified user. /// @@ -35,7 +31,7 @@ namespace osu.Game.Users.Drawables private void load(LargeTextureStore textures) { string avatarUrl = user?.AvatarUrl; - if (api != null && avatarUrl != null) + if (avatarUrl != null) Texture = textures.Get(avatarUrl); else if (user != null && user.Id > 1) Texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); From 015cf5f7eba34442e07fd888af802443e3999075 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 13:22:48 +0900 Subject: [PATCH 0237/1621] 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 0c1f624b56f662bedaee8b530fa9584e9753b59b Mon Sep 17 00:00:00 2001 From: ilsubyeega Date: Tue, 30 Mar 2021 17:03:01 +0900 Subject: [PATCH 0238/1621] Simply code under assuming that avatarUrl always non-null --- osu.Game/Users/Drawables/DrawableAvatar.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs index 930f1c07a1..ef074813a5 100644 --- a/osu.Game/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -30,11 +30,8 @@ namespace osu.Game.Users.Drawables [BackgroundDependencyLoader] private void load(LargeTextureStore textures) { - string avatarUrl = user?.AvatarUrl; - if (avatarUrl != null) - Texture = textures.Get(avatarUrl); - else if (user != null && user.Id > 1) - Texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); + if (user != null && user.Id > 1) + Texture = textures.Get(user.AvatarUrl); Texture ??= textures.Get(@"Online/avatar-guest"); } From 1281273dd32d7fb47ad4e943a46d7d8099b8b7e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 22:46:20 +0900 Subject: [PATCH 0239/1621] 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 0240/1621] 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 0241/1621] 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 0242/1621] 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 0243/1621] 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 0244/1621] 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 0245/1621] 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 0246/1621] 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 0247/1621] 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 961bd1177c9bfbeb3203aab22a4602fee3d3f6b2 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 25 Apr 2021 00:39:36 +0200 Subject: [PATCH 0248/1621] Add mod "Random" for ruleset "osu!" --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 77 ++++++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + osu.Game/Rulesets/Mods/ModRandomOsu.cs | 47 +++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs create mode 100644 osu.Game/Rulesets/Mods/ModRandomOsu.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs new file mode 100644 index 0000000000..f3c9040b1c --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModRandom : ModRandomOsu + { + protected override void RandomiseHitObjectPositions(IBeatmap beatmap) + { + var rng = new Random(); + + foreach (var hitObject in beatmap.HitObjects) + { + if (RandomiseCirclePositions.Value && hitObject is HitCircle circle) + { + circle.Position = new Vector2( + (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.X, + (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y + ); + } + else if (RandomiseSpinnerPositions.Value && hitObject is Spinner spinner) + { + spinner.Position = new Vector2( + (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.X, + (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y + ); + } + else if (RandomiseSliderPositions.Value && hitObject is Slider slider) + { + // Min. distances from the slider's position to the border to prevent the slider from being partially out of the screen + float minLeft = 0, minRight = 0, minTop = 0, minBottom = 0; + + var controlPointPositions = (from position + in slider.Path.ControlPoints + select position.Position.Value).ToList(); + + controlPointPositions.Add(slider.EndPosition); + controlPointPositions.RemoveAt(controlPointPositions.Count - 1); + + foreach (var position in controlPointPositions) + { + if (position.X > minRight) + { + minRight = position.X; + } + else if (-position.X > minLeft) + { + minLeft = -position.X; + } + + if (position.Y > minBottom) + { + minBottom = position.Y; + } + else if (-position.Y > minTop) + { + minTop = -position.Y; + } + } + + slider.Position = new Vector2( + (float)rng.NextDouble() * (OsuPlayfield.BASE_SIZE.X - minLeft - minRight) + minLeft, + (float)rng.NextDouble() * (OsuPlayfield.BASE_SIZE.Y - minTop - minBottom) + minTop + ); + } + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 465d6d7155..6a04c4ca5c 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -186,6 +186,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new ModWindUp(), new ModWindDown()), new OsuModTraceable(), new OsuModBarrelRoll(), + new OsuModRandom(), }; case ModType.System: diff --git a/osu.Game/Rulesets/Mods/ModRandomOsu.cs b/osu.Game/Rulesets/Mods/ModRandomOsu.cs new file mode 100644 index 0000000000..9fb2c07d82 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModRandomOsu.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 osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModRandomOsu : Mod, IApplicableToBeatmap + { + public override string Name => "Random"; + public override string Acronym => "RD"; + public override IconUsage? Icon => OsuIcon.Dice; + public override ModType Type => ModType.Fun; + public override string Description => "Hit objects appear at random positions"; + public override double ScoreMultiplier => 1; + public override bool Ranked => false; + + [SettingSource("Randomise circle positions", "Hit circles appear at random positions")] + public Bindable RandomiseCirclePositions { get; } = new BindableBool + { + Default = true, + Value = true, + }; + + [SettingSource("Randomise slider positions", "Sliders appear at random positions")] + public Bindable RandomiseSliderPositions { get; } = new BindableBool + { + Default = true, + Value = true, + }; + + [SettingSource("Randomise spinner positions", "Spinners appear at random positions")] + public Bindable RandomiseSpinnerPositions { get; } = new BindableBool + { + Default = true, + Value = true, + }; + + public void ApplyToBeatmap(IBeatmap beatmap) => RandomiseHitObjectPositions(beatmap); + + protected abstract void RandomiseHitObjectPositions(IBeatmap beatmap); + } +} From 817bb5213c48d0b4cf5f94a375c74e04c1a16ff5 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 25 Apr 2021 00:46:35 +0200 Subject: [PATCH 0249/1621] Make OsuAutoGenerator spin the cursor around the position of the spinner instead of a set value This is to make Autoplay work with randomised spinner positions --- .../Replays/OsuAutoGenerator.cs | 18 +++++++++--------- .../Replays/OsuAutoGeneratorBase.cs | 3 --- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 7b0cf651c8..609799dc54 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -155,9 +155,9 @@ namespace osu.Game.Rulesets.Osu.Replays if (spinner.SpinsRequired == 0) return; - calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection); + calcSpinnerStartPosAndDirection(spinner, ((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection); - Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[^1]).Position; + Vector2 spinCentreOffset = spinner.Position - ((OsuReplayFrame)Frames[^1]).Position; if (spinCentreOffset.Length > SPIN_RADIUS) { @@ -180,9 +180,9 @@ namespace osu.Game.Rulesets.Osu.Replays #region Helper subroutines - private static void calcSpinnerStartPosAndDirection(Vector2 prevPos, out Vector2 startPosition, out float spinnerDirection) + private static void calcSpinnerStartPosAndDirection(Spinner spinner, Vector2 prevPos, out Vector2 startPosition, out float spinnerDirection) { - Vector2 spinCentreOffset = SPINNER_CENTRE - prevPos; + Vector2 spinCentreOffset = spinner.Position - prevPos; float distFromCentre = spinCentreOffset.Length; float distToTangentPoint = MathF.Sqrt(distFromCentre * distFromCentre - SPIN_RADIUS * SPIN_RADIUS); @@ -216,13 +216,13 @@ namespace osu.Game.Rulesets.Osu.Replays else if (spinCentreOffset.Length > 0) { // Previous cursor position was inside spin circle, set startPosition to the nearest point on spin circle. - startPosition = SPINNER_CENTRE - spinCentreOffset * (SPIN_RADIUS / spinCentreOffset.Length); + startPosition = spinner.Position - spinCentreOffset * (SPIN_RADIUS / spinCentreOffset.Length); spinnerDirection = 1; } else { // Degenerate case where cursor position is exactly at the centre of the spin circle. - startPosition = SPINNER_CENTRE + new Vector2(0, -SPIN_RADIUS); + startPosition = spinner.Position + new Vector2(0, -SPIN_RADIUS); spinnerDirection = 1; } } @@ -335,7 +335,7 @@ namespace osu.Game.Rulesets.Osu.Replays { // We add intermediate frames for spinning / following a slider here. case Spinner spinner: - Vector2 difference = startPosition - SPINNER_CENTRE; + Vector2 difference = startPosition - spinner.Position; float radius = difference.Length; float angle = radius == 0 ? 0 : MathF.Atan2(difference.Y, difference.X); @@ -348,7 +348,7 @@ namespace osu.Game.Rulesets.Osu.Replays t = ApplyModsToTimeDelta(previousFrame, nextFrame) * spinnerDirection; angle += (float)t / 20; - Vector2 pos = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS); + Vector2 pos = spinner.Position + CirclePosition(angle, SPIN_RADIUS); AddFrameToReplay(new OsuReplayFrame((int)nextFrame, new Vector2(pos.X, pos.Y), action)); previousFrame = nextFrame; @@ -357,7 +357,7 @@ namespace osu.Game.Rulesets.Osu.Replays t = ApplyModsToTimeDelta(previousFrame, spinner.EndTime) * spinnerDirection; angle += (float)t / 20; - Vector2 endPosition = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS); + Vector2 endPosition = spinner.Position + CirclePosition(angle, SPIN_RADIUS); AddFrameToReplay(new OsuReplayFrame(spinner.EndTime, new Vector2(endPosition.X, endPosition.Y), action)); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 1cb3208c30..69eb669a8e 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Replays; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Osu.Replays @@ -20,8 +19,6 @@ namespace osu.Game.Rulesets.Osu.Replays /// /// Constants (for spinners). /// - protected static readonly Vector2 SPINNER_CENTRE = OsuPlayfield.BASE_SIZE / 2; - public const float SPIN_RADIUS = 50; #endregion From 8a3fa53c2661662272a62699eed7d7a1f04407ea Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 25 Apr 2021 01:02:03 +0200 Subject: [PATCH 0250/1621] Change mod description and settings labels --- osu.Game/Rulesets/Mods/ModRandomOsu.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModRandomOsu.cs b/osu.Game/Rulesets/Mods/ModRandomOsu.cs index 9fb2c07d82..6b86da357a 100644 --- a/osu.Game/Rulesets/Mods/ModRandomOsu.cs +++ b/osu.Game/Rulesets/Mods/ModRandomOsu.cs @@ -15,25 +15,25 @@ namespace osu.Game.Rulesets.Mods public override string Acronym => "RD"; public override IconUsage? Icon => OsuIcon.Dice; public override ModType Type => ModType.Fun; - public override string Description => "Hit objects appear at random positions"; + public override string Description => "Practice your reaction time!"; public override double ScoreMultiplier => 1; public override bool Ranked => false; - [SettingSource("Randomise circle positions", "Hit circles appear at random positions")] + [SettingSource("Circles", "Hit circles appear at random positions")] public Bindable RandomiseCirclePositions { get; } = new BindableBool { Default = true, Value = true, }; - [SettingSource("Randomise slider positions", "Sliders appear at random positions")] + [SettingSource("Sliders", "Sliders appear at random positions")] public Bindable RandomiseSliderPositions { get; } = new BindableBool { Default = true, Value = true, }; - [SettingSource("Randomise spinner positions", "Spinners appear at random positions")] + [SettingSource("Spinners", "Spinners appear at random positions")] public Bindable RandomiseSpinnerPositions { get; } = new BindableBool { Default = true, From 92f765b9588e6de084b4d57d7e00a75a210c07ec Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 25 Apr 2021 01:19:06 +0200 Subject: [PATCH 0251/1621] Change ModType from Fun to Conversion --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 ++-- osu.Game/Rulesets/Mods/ModRandomOsu.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 6a04c4ca5c..b50d3ad2b4 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -164,7 +164,8 @@ namespace osu.Game.Rulesets.Osu { new OsuModTarget(), new OsuModDifficultyAdjust(), - new OsuModClassic() + new OsuModClassic(), + new OsuModRandom(), }; case ModType.Automation: @@ -186,7 +187,6 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new ModWindUp(), new ModWindDown()), new OsuModTraceable(), new OsuModBarrelRoll(), - new OsuModRandom(), }; case ModType.System: diff --git a/osu.Game/Rulesets/Mods/ModRandomOsu.cs b/osu.Game/Rulesets/Mods/ModRandomOsu.cs index 6b86da357a..1581065c01 100644 --- a/osu.Game/Rulesets/Mods/ModRandomOsu.cs +++ b/osu.Game/Rulesets/Mods/ModRandomOsu.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Random"; public override string Acronym => "RD"; public override IconUsage? Icon => OsuIcon.Dice; - public override ModType Type => ModType.Fun; + public override ModType Type => ModType.Conversion; public override string Description => "Practice your reaction time!"; public override double ScoreMultiplier => 1; public override bool Ranked => false; From f33f1b2bed13faebb9ad8d041077f172178e60fc Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 25 Apr 2021 01:34:39 +0200 Subject: [PATCH 0252/1621] Remove class "ModRandomOsu" and adjust code Add documentation comment for OsuModRandom --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 34 +++++++++++++++- osu.Game/Rulesets/Mods/ModRandomOsu.cs | 47 ---------------------- 2 files changed, 32 insertions(+), 49 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/ModRandomOsu.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index f3c9040b1c..c87628b0e7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -3,17 +3,47 @@ using System; using System.Linq; +using osu.Framework.Bindables; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModRandom : ModRandomOsu + /// + /// Mod that randomises the positions of the s + /// + public class OsuModRandom : ModRandom, IApplicableToBeatmap { - protected override void RandomiseHitObjectPositions(IBeatmap beatmap) + public override string Description => "Practice your reaction time!"; + public override bool Ranked => false; + + [SettingSource("Circles", "Hit circles appear at random positions")] + public Bindable RandomiseCirclePositions { get; } = new BindableBool + { + Default = true, + Value = true, + }; + + [SettingSource("Sliders", "Sliders appear at random positions")] + public Bindable RandomiseSliderPositions { get; } = new BindableBool + { + Default = true, + Value = true, + }; + + [SettingSource("Spinners", "Spinners appear at random positions")] + public Bindable RandomiseSpinnerPositions { get; } = new BindableBool + { + Default = true, + Value = true, + }; + + public void ApplyToBeatmap(IBeatmap beatmap) { var rng = new Random(); diff --git a/osu.Game/Rulesets/Mods/ModRandomOsu.cs b/osu.Game/Rulesets/Mods/ModRandomOsu.cs deleted file mode 100644 index 1581065c01..0000000000 --- a/osu.Game/Rulesets/Mods/ModRandomOsu.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.Bindables; -using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModRandomOsu : Mod, IApplicableToBeatmap - { - public override string Name => "Random"; - public override string Acronym => "RD"; - public override IconUsage? Icon => OsuIcon.Dice; - public override ModType Type => ModType.Conversion; - public override string Description => "Practice your reaction time!"; - public override double ScoreMultiplier => 1; - public override bool Ranked => false; - - [SettingSource("Circles", "Hit circles appear at random positions")] - public Bindable RandomiseCirclePositions { get; } = new BindableBool - { - Default = true, - Value = true, - }; - - [SettingSource("Sliders", "Sliders appear at random positions")] - public Bindable RandomiseSliderPositions { get; } = new BindableBool - { - Default = true, - Value = true, - }; - - [SettingSource("Spinners", "Spinners appear at random positions")] - public Bindable RandomiseSpinnerPositions { get; } = new BindableBool - { - Default = true, - Value = true, - }; - - public void ApplyToBeatmap(IBeatmap beatmap) => RandomiseHitObjectPositions(beatmap); - - protected abstract void RandomiseHitObjectPositions(IBeatmap beatmap); - } -} From 08821da954d3927d776534fa1550d23838b3855a Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 25 Apr 2021 01:43:32 +0200 Subject: [PATCH 0253/1621] Change mod description --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index c87628b0e7..40e966a686 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// public class OsuModRandom : ModRandom, IApplicableToBeatmap { - public override string Description => "Practice your reaction time!"; + public override string Description => "It never gets boring!"; public override bool Ranked => false; [SettingSource("Circles", "Hit circles appear at random positions")] From 6e85c4e0699e85df8ebf841404cfd9e1dd652bae Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 25 Apr 2021 23:57:01 +0200 Subject: [PATCH 0254/1621] Change randomisation process to keep distances between objects Remove now unnecessary settings; Remove spinners; Sliders are not implemented yet --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 254 +++++++++++++++------ 1 file changed, 183 insertions(+), 71 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 40e966a686..1d430ba711 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -2,10 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; -using osu.Framework.Bindables; using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; @@ -22,86 +19,201 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "It never gets boring!"; public override bool Ranked => false; - [SettingSource("Circles", "Hit circles appear at random positions")] - public Bindable RandomiseCirclePositions { get; } = new BindableBool - { - Default = true, - Value = true, - }; - - [SettingSource("Sliders", "Sliders appear at random positions")] - public Bindable RandomiseSliderPositions { get; } = new BindableBool - { - Default = true, - Value = true, - }; - - [SettingSource("Spinners", "Spinners appear at random positions")] - public Bindable RandomiseSpinnerPositions { get; } = new BindableBool - { - Default = true, - Value = true, - }; + // The distances from the hit objects to the borders of the playfield they start to "turn around" and curve towards the middle. + // The closer the hit objects draw to the border, the sharper the turn + private const byte border_distance_x = 128; + private const byte border_distance_y = 96; public void ApplyToBeatmap(IBeatmap beatmap) { var rng = new Random(); - foreach (var hitObject in beatmap.HitObjects) + // Absolute angle + float prevAngleRad = 0; + + // Absolute positions + Vector2 prevPosUnchanged = ((OsuHitObject)beatmap.HitObjects[0]).Position; + Vector2 prevPosChanged = ((OsuHitObject)beatmap.HitObjects[0]).Position; + + // rateOfChangeMultiplier changes every i iterations to prevent shaky-line-shaped streams + byte i = 5; + float rateOfChangeMultiplier = 0; + + foreach (var beatmapHitObject in beatmap.HitObjects) { - if (RandomiseCirclePositions.Value && hitObject is HitCircle circle) + if (!(beatmapHitObject is OsuHitObject hitObject)) + return; + + // posUnchanged: position from the original beatmap (not randomised) + var posUnchanged = hitObject.EndPosition; + var posChanged = Vector2.Zero; + + // Angle of the vector pointing from the last to the current hit object + float angleRad = 0; + + if (i >= 5) { - circle.Position = new Vector2( - (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.X, - (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y + i = 0; + rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; + } + + if (hitObject is HitCircle circle) + { + var distanceToPrev = Vector2.Distance(posUnchanged, prevPosUnchanged); + + circle.Position = posChanged = getRandomisedPosition( + rateOfChangeMultiplier, + prevPosChanged, + prevAngleRad, + distanceToPrev, + out angleRad ); } - else if (RandomiseSpinnerPositions.Value && hitObject is Spinner spinner) - { - spinner.Position = new Vector2( - (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.X, - (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y - ); - } - else if (RandomiseSliderPositions.Value && hitObject is Slider slider) - { - // Min. distances from the slider's position to the border to prevent the slider from being partially out of the screen - float minLeft = 0, minRight = 0, minTop = 0, minBottom = 0; - var controlPointPositions = (from position - in slider.Path.ControlPoints - select position.Position.Value).ToList(); + // TODO: Implement slider position randomisation - controlPointPositions.Add(slider.EndPosition); - controlPointPositions.RemoveAt(controlPointPositions.Count - 1); - - foreach (var position in controlPointPositions) - { - if (position.X > minRight) - { - minRight = position.X; - } - else if (-position.X > minLeft) - { - minLeft = -position.X; - } - - if (position.Y > minBottom) - { - minBottom = position.Y; - } - else if (-position.Y > minTop) - { - minTop = -position.Y; - } - } - - slider.Position = new Vector2( - (float)rng.NextDouble() * (OsuPlayfield.BASE_SIZE.X - minLeft - minRight) + minLeft, - (float)rng.NextDouble() * (OsuPlayfield.BASE_SIZE.Y - minTop - minBottom) + minTop - ); - } + prevAngleRad = angleRad; + prevPosUnchanged = posUnchanged; + prevPosChanged = posChanged; + i++; } } + + /// + /// Returns the final position of the hit object + /// + /// Final position of the hit object + private Vector2 getRandomisedPosition( + float rateOfChangeMultiplier, + Vector2 prevPosChanged, + float prevAngleRad, + float distanceToPrev, + out float newAngle) + { + // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) + // is proportional to the distance between the last and the current hit object + // to allow jumps and prevent too sharp turns during streams. + var maxDistance = OsuPlayfield.BASE_SIZE.LengthFast; + var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * distanceToPrev / maxDistance; + + newAngle = (float)randomAngleRad + prevAngleRad; + if (newAngle < 0) + newAngle += 2 * (float)Math.PI; + + var posRelativeToPrev = new Vector2( + distanceToPrev * (float)Math.Cos(newAngle), + distanceToPrev * (float)Math.Sin(newAngle) + ); + + posRelativeToPrev = getRotatedVector(prevPosChanged, posRelativeToPrev); + + newAngle = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); + var position = Vector2.Add(prevPosChanged, posRelativeToPrev); + + // Move hit objects back into the playfield if they are outside of it, + // which would sometimes happen during big jumps otherwise. + if (position.X < 0) + position.X = 0; + else if (position.X > OsuPlayfield.BASE_SIZE.X) + position.X = OsuPlayfield.BASE_SIZE.X; + + if (position.Y < 0) + position.Y = 0; + else if (position.Y > OsuPlayfield.BASE_SIZE.Y) + position.Y = OsuPlayfield.BASE_SIZE.Y; + + return position; + } + + /// + /// Determines the position of the current hit object relative to the previous one. + /// + /// The position of the current hit object relative to the previous one + private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev) + { + var relativeRotationDistance = 0f; + var playfieldMiddle = Vector2.Divide(OsuPlayfield.BASE_SIZE, 2); + + if (prevPosChanged.X < playfieldMiddle.X) + { + relativeRotationDistance = Math.Max( + (border_distance_x - prevPosChanged.X) / border_distance_x, + relativeRotationDistance + ); + } + else + { + relativeRotationDistance = Math.Max( + (prevPosChanged.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x, + relativeRotationDistance + ); + } + + if (prevPosChanged.Y < playfieldMiddle.Y) + { + relativeRotationDistance = Math.Max( + (border_distance_y - prevPosChanged.Y) / border_distance_y, + relativeRotationDistance + ); + } + else + { + relativeRotationDistance = Math.Max( + (prevPosChanged.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y, + relativeRotationDistance + ); + } + + return rotateVectorTowardsVector( + posRelativeToPrev, + Vector2.Subtract(playfieldMiddle, prevPosChanged), + relativeRotationDistance + ); + } + + /// + /// Rotates vector "initial" towards vector "destinantion" + /// + /// Vector to rotate to "destination" + /// Vector "initial" should be rotated to + /// The angle the vector should be rotated relative to the difference between the angles of the the two vectors. + /// Resulting vector + private Vector2 rotateVectorTowardsVector(Vector2 initial, Vector2 destination, float relativeDistance) + { + var initialAngleRad = Math.Atan2(initial.Y, initial.X); + var destAngleRad = Math.Atan2(destination.Y, destination.X); + + // Divide by 2 to limit the max. angle to 90° + // (90° is enough to prevent the hit objects from leaving the playfield) + relativeDistance /= 2; + + var diff = destAngleRad - initialAngleRad; + + while (diff < -Math.PI) + { + diff += 2 * Math.PI; + } + + while (diff > Math.PI) + { + diff -= 2 * Math.PI; + } + + var finalAngle = 0d; + + if (diff > 0) + { + finalAngle = initialAngleRad + relativeDistance * diff; + } + else if (diff < 0) + { + finalAngle = initialAngleRad + relativeDistance * diff; + } + + return new Vector2( + initial.Length * (float)Math.Cos(finalAngle), + initial.Length * (float)Math.Sin(finalAngle) + ); + } } } 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 0255/1621] 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 0256/1621] 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 0257/1621] 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 0258/1621] 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 0259/1621] 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 0260/1621] 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 0261/1621] 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 0262/1621] 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 19fc2243489235598d523104296ec51448d6d897 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 26 Apr 2021 11:52:46 +0200 Subject: [PATCH 0263/1621] Revert "Make OsuAutoGenerator spin the cursor around the position of the spinner instead of a set value" This reverts commit 817bb5213c48d0b4cf5f94a375c74e04c1a16ff5. --- .../Replays/OsuAutoGenerator.cs | 18 +++++++++--------- .../Replays/OsuAutoGeneratorBase.cs | 3 +++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 609799dc54..7b0cf651c8 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -155,9 +155,9 @@ namespace osu.Game.Rulesets.Osu.Replays if (spinner.SpinsRequired == 0) return; - calcSpinnerStartPosAndDirection(spinner, ((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection); + calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection); - Vector2 spinCentreOffset = spinner.Position - ((OsuReplayFrame)Frames[^1]).Position; + Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[^1]).Position; if (spinCentreOffset.Length > SPIN_RADIUS) { @@ -180,9 +180,9 @@ namespace osu.Game.Rulesets.Osu.Replays #region Helper subroutines - private static void calcSpinnerStartPosAndDirection(Spinner spinner, Vector2 prevPos, out Vector2 startPosition, out float spinnerDirection) + private static void calcSpinnerStartPosAndDirection(Vector2 prevPos, out Vector2 startPosition, out float spinnerDirection) { - Vector2 spinCentreOffset = spinner.Position - prevPos; + Vector2 spinCentreOffset = SPINNER_CENTRE - prevPos; float distFromCentre = spinCentreOffset.Length; float distToTangentPoint = MathF.Sqrt(distFromCentre * distFromCentre - SPIN_RADIUS * SPIN_RADIUS); @@ -216,13 +216,13 @@ namespace osu.Game.Rulesets.Osu.Replays else if (spinCentreOffset.Length > 0) { // Previous cursor position was inside spin circle, set startPosition to the nearest point on spin circle. - startPosition = spinner.Position - spinCentreOffset * (SPIN_RADIUS / spinCentreOffset.Length); + startPosition = SPINNER_CENTRE - spinCentreOffset * (SPIN_RADIUS / spinCentreOffset.Length); spinnerDirection = 1; } else { // Degenerate case where cursor position is exactly at the centre of the spin circle. - startPosition = spinner.Position + new Vector2(0, -SPIN_RADIUS); + startPosition = SPINNER_CENTRE + new Vector2(0, -SPIN_RADIUS); spinnerDirection = 1; } } @@ -335,7 +335,7 @@ namespace osu.Game.Rulesets.Osu.Replays { // We add intermediate frames for spinning / following a slider here. case Spinner spinner: - Vector2 difference = startPosition - spinner.Position; + Vector2 difference = startPosition - SPINNER_CENTRE; float radius = difference.Length; float angle = radius == 0 ? 0 : MathF.Atan2(difference.Y, difference.X); @@ -348,7 +348,7 @@ namespace osu.Game.Rulesets.Osu.Replays t = ApplyModsToTimeDelta(previousFrame, nextFrame) * spinnerDirection; angle += (float)t / 20; - Vector2 pos = spinner.Position + CirclePosition(angle, SPIN_RADIUS); + Vector2 pos = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS); AddFrameToReplay(new OsuReplayFrame((int)nextFrame, new Vector2(pos.X, pos.Y), action)); previousFrame = nextFrame; @@ -357,7 +357,7 @@ namespace osu.Game.Rulesets.Osu.Replays t = ApplyModsToTimeDelta(previousFrame, spinner.EndTime) * spinnerDirection; angle += (float)t / 20; - Vector2 endPosition = spinner.Position + CirclePosition(angle, SPIN_RADIUS); + Vector2 endPosition = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS); AddFrameToReplay(new OsuReplayFrame(spinner.EndTime, new Vector2(endPosition.X, endPosition.Y), action)); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 69eb669a8e..1cb3208c30 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Replays; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Osu.Replays @@ -19,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Replays /// /// Constants (for spinners). /// + protected static readonly Vector2 SPINNER_CENTRE = OsuPlayfield.BASE_SIZE / 2; + public const float SPIN_RADIUS = 50; #endregion From 5f6fd9ba13a81a4f308a19ab3021e2bdaa525b26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 19:07:42 +0900 Subject: [PATCH 0264/1621] 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 1dfe028c0209d4ef4cac6c3c2462af22797d81a9 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 26 Apr 2021 22:26:13 +0200 Subject: [PATCH 0265/1621] Fix bug causing the star rating to change when Random is enabled --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 10 ++++++++++ .../Mods/IApplicableToBeatmapKeepStarRating.cs | 14 ++++++++++++++ osu.Game/Screens/Play/Player.cs | 2 ++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToBeatmapKeepStarRating.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 1d430ba711..3a4b5073b5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Mod that randomises the positions of the s /// - public class OsuModRandom : ModRandom, IApplicableToBeatmap + public class OsuModRandom : ModRandom, IApplicableToBeatmapKeepStarRating { public override string Description => "It never gets boring!"; public override bool Ranked => false; diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index e0eeaf6db0..d49f6ed50b 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -32,6 +32,11 @@ namespace osu.Game.Beatmaps public readonly BeatmapMetadata Metadata; + /// + /// Only if this is set to true, changes made by mods that implement will be applied. + /// + public bool ApplyChangesToBeatmap; + protected AudioManager AudioManager { get; } protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager) @@ -166,10 +171,15 @@ namespace osu.Game.Beatmaps foreach (var mod in mods.OfType()) { + if (mod is IApplicableToBeatmapKeepStarRating && !ApplyChangesToBeatmap) + continue; + cancellationSource.Token.ThrowIfCancellationRequested(); mod.ApplyToBeatmap(converted); } + ApplyChangesToBeatmap = false; + return converted; } } diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapKeepStarRating.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapKeepStarRating.cs new file mode 100644 index 0000000000..34db7e1be3 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapKeepStarRating.cs @@ -0,0 +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 osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// Interface for a that applies changes to a after conversion and post-processing has completed without changing its difficulty + /// + public interface IApplicableToBeatmapKeepStarRating : IApplicableToBeatmap + { + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 27a4fcc291..80eec64884 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -455,6 +455,8 @@ namespace osu.Game.Screens.Play rulesetInfo = Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset; ruleset = rulesetInfo.CreateInstance(); + Beatmap.Value.ApplyChangesToBeatmap = true; + try { playable = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Mods.Value); From 4b05568d2d0159e98eb0b7664e87bad48fe8200a Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Tue, 27 Apr 2021 19:39:58 +0200 Subject: [PATCH 0266/1621] Revert "Fix bug causing the star rating to change when Random is enabled" This reverts commit 1dfe028c0209d4ef4cac6c3c2462af22797d81a9. --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 10 ---------- .../Mods/IApplicableToBeatmapKeepStarRating.cs | 14 -------------- osu.Game/Screens/Play/Player.cs | 2 -- 4 files changed, 1 insertion(+), 27 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/IApplicableToBeatmapKeepStarRating.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 3a4b5073b5..1d430ba711 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Mod that randomises the positions of the s /// - public class OsuModRandom : ModRandom, IApplicableToBeatmapKeepStarRating + public class OsuModRandom : ModRandom, IApplicableToBeatmap { public override string Description => "It never gets boring!"; public override bool Ranked => false; diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index d49f6ed50b..e0eeaf6db0 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -32,11 +32,6 @@ namespace osu.Game.Beatmaps public readonly BeatmapMetadata Metadata; - /// - /// Only if this is set to true, changes made by mods that implement will be applied. - /// - public bool ApplyChangesToBeatmap; - protected AudioManager AudioManager { get; } protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager) @@ -171,15 +166,10 @@ namespace osu.Game.Beatmaps foreach (var mod in mods.OfType()) { - if (mod is IApplicableToBeatmapKeepStarRating && !ApplyChangesToBeatmap) - continue; - cancellationSource.Token.ThrowIfCancellationRequested(); mod.ApplyToBeatmap(converted); } - ApplyChangesToBeatmap = false; - return converted; } } diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapKeepStarRating.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapKeepStarRating.cs deleted file mode 100644 index 34db7e1be3..0000000000 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmapKeepStarRating.cs +++ /dev/null @@ -1,14 +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.Beatmaps; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// Interface for a that applies changes to a after conversion and post-processing has completed without changing its difficulty - /// - public interface IApplicableToBeatmapKeepStarRating : IApplicableToBeatmap - { - } -} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 80eec64884..27a4fcc291 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -455,8 +455,6 @@ namespace osu.Game.Screens.Play rulesetInfo = Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset; ruleset = rulesetInfo.CreateInstance(); - Beatmap.Value.ApplyChangesToBeatmap = true; - try { playable = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Mods.Value); From a141a4e9e6641e322740240258179d5b06718b0b Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Tue, 27 Apr 2021 20:44:36 +0200 Subject: [PATCH 0267/1621] Add setting "Seed" Random numbers are now generated with the seed specified in the mod settings. --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 1d430ba711..675aa4a0b3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -2,7 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; +using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; @@ -24,9 +27,23 @@ namespace osu.Game.Rulesets.Osu.Mods private const byte border_distance_x = 128; private const byte border_distance_y = 96; + [SettingSource("Seed", "Seed for the random number generator")] + public Bindable Seed { get; } = new Bindable + { + Value = "0" + }; + public void ApplyToBeatmap(IBeatmap beatmap) { - var rng = new Random(); + if (!int.TryParse(Seed.Value, out var seed)) + { + var e = new FormatException("Seed must be an integer"); + Logger.Error(e, "Could not load beatmap: RNG seed must be an integer."); + + return; + } + + var rng = new Random(seed); // Absolute angle float prevAngleRad = 0; From 95040f7edc42262fd43cfddac8a4315bc0c402a6 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Tue, 27 Apr 2021 22:19:04 +0200 Subject: [PATCH 0268/1621] Change initial seed to a random number --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 675aa4a0b3..0124a3c28e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; @@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Seed", "Seed for the random number generator")] public Bindable Seed { get; } = new Bindable { - Value = "0" + Value = RNG.Next().ToString() }; public void ApplyToBeatmap(IBeatmap beatmap) From 6bed268bd8ff7719b36bdba5e253de0291c6deaa Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 1 May 2021 04:01:43 +0200 Subject: [PATCH 0269/1621] Enhance mod settings and add option "Random seed" + slight adjustments --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 161 ++++++++++++++++++--- 1 file changed, 144 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 0124a3c28e..9a6127cdad 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -2,13 +2,20 @@ // 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.Logging; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Platform; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osuTK; @@ -25,36 +32,58 @@ namespace osu.Game.Rulesets.Osu.Mods // The distances from the hit objects to the borders of the playfield they start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn - private const byte border_distance_x = 128; - private const byte border_distance_y = 96; + private const byte border_distance_x = 192; + private const byte border_distance_y = 144; - [SettingSource("Seed", "Seed for the random number generator")] - public Bindable Seed { get; } = new Bindable + private static readonly Bindable seed = new Bindable { - Value = RNG.Next().ToString() + Default = -1 }; - public void ApplyToBeatmap(IBeatmap beatmap) + private static readonly BindableBool random_seed = new BindableBool { - if (!int.TryParse(Seed.Value, out var seed)) - { - var e = new FormatException("Seed must be an integer"); - Logger.Error(e, "Could not load beatmap: RNG seed must be an integer."); + Value = true, + Default = true + }; + [SettingSource("Random seed", "Generate a random seed for the beatmap generation")] + public BindableBool RandomSeed => random_seed; + + [SettingSource("Seed", "Seed for the random beatmap generation", SettingControlType = typeof(OsuModRandomSettingsControl))] + public Bindable Seed => seed; + + internal static bool CustomSeedDisabled => random_seed.Value; + + public OsuModRandom() + { + if (seed.Default != -1) return; - } - var rng = new Random(seed); + var random = RNG.Next(); + seed.Value = random; + seed.Default = random; + seed.BindValueChanged(e => seed.Default = e.NewValue); + } + + public void ApplyToBeatmap(IBeatmap iBeatmap) + { + if (!(iBeatmap is OsuBeatmap beatmap)) + return; + + if (RandomSeed.Value) + seed.Value = RNG.Next(); + + var rng = new Random(seed.Value); // Absolute angle float prevAngleRad = 0; // Absolute positions - Vector2 prevPosUnchanged = ((OsuHitObject)beatmap.HitObjects[0]).Position; - Vector2 prevPosChanged = ((OsuHitObject)beatmap.HitObjects[0]).Position; + Vector2 prevPosUnchanged = beatmap.HitObjects[0].Position; + Vector2 prevPosChanged = beatmap.HitObjects[0].Position; // rateOfChangeMultiplier changes every i iterations to prevent shaky-line-shaped streams - byte i = 5; + byte i = 3; float rateOfChangeMultiplier = 0; foreach (var beatmapHitObject in beatmap.HitObjects) @@ -69,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Mods // Angle of the vector pointing from the last to the current hit object float angleRad = 0; - if (i >= 5) + if (i >= 3) { i = 0; rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; @@ -234,4 +263,102 @@ namespace osu.Game.Rulesets.Osu.Mods ); } } + + public class OsuModRandomSettingsControl : SettingsItem + { + [Resolved] + private static GameHost host { get; set; } + + [BackgroundDependencyLoader] + private void load(GameHost gameHost) => host = gameHost; + + protected override Drawable CreateControl() => new SeedControl + { + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 5 } + }; + + private sealed class SeedControl : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current; + set => Scheduler.Add(() => current.Current = value); + } + + private readonly OsuNumberBox seedNumberBox; + + public SeedControl() + { + AutoSizeAxes = Axes.Y; + + InternalChildren = new[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(GridSizeMode.Relative, 0.25f) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + seedNumberBox = new OsuNumberBox + { + RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true + }, + null, + new TriangleButton + { + RelativeSizeAxes = Axes.Both, + Height = 1, + Text = "Copy", + Action = copySeedToClipboard + } + } + } + } + }; + + seedNumberBox.Current.BindValueChanged(onTextBoxValueChanged); + } + + private void onTextBoxValueChanged(ValueChangedEvent e) + { + string seed = e.NewValue; + + while (!string.IsNullOrEmpty(seed) && !int.TryParse(seed, out _)) + seed = seed[..^1]; + + if (!int.TryParse(seed, out var intVal)) + intVal = 0; + + current.Value = intVal; + } + + private void copySeedToClipboard() => host.GetClipboard().SetText(seedNumberBox.Text); + + protected override void Update() + { + seedNumberBox.ReadOnly = OsuModRandom.CustomSeedDisabled; + + if (seedNumberBox.HasFocus) + return; + + seedNumberBox.Text = current.Current.Value.ToString(); + } + } + } } From f95744170f008e5aecf912a4a380e6191296089b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 07:06:26 +0300 Subject: [PATCH 0270/1621] Add skin config lookup for combo colours --- osu.Game/Skinning/SkinComboColourLookup.cs | 26 +++++++++++++++++++++ osu.Game/Skinning/SkinProvidingContainer.cs | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Skinning/SkinComboColourLookup.cs diff --git a/osu.Game/Skinning/SkinComboColourLookup.cs b/osu.Game/Skinning/SkinComboColourLookup.cs new file mode 100644 index 0000000000..33e35a96fb --- /dev/null +++ b/osu.Game/Skinning/SkinComboColourLookup.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.Game.Rulesets.Objects.Types; + +namespace osu.Game.Skinning +{ + public class SkinComboColourLookup + { + /// + /// The index to use for deciding the combo colour. + /// + public readonly int ColourIndex; + + /// + /// The combo information requesting the colour. + /// + public readonly IHasComboInformation Combo; + + public SkinComboColourLookup(int colourIndex, IHasComboInformation combo) + { + ColourIndex = colourIndex; + Combo = combo; + } + } +} diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index cf22b2e820..a7fc1306c9 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -72,7 +72,7 @@ namespace osu.Game.Skinning { if (skin != null) { - if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) + if (lookup is GlobalSkinColours || lookup is SkinComboColourLookup || lookup is SkinCustomColourLookup) return lookupWithFallback(lookup, AllowColourLookup); return lookupWithFallback(lookup, AllowConfigurationLookup); From 9be8d3f0d21b77a63110ea0335de65a3ce0dfc06 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 07:12:17 +0300 Subject: [PATCH 0271/1621] Add overridable combo colour retrieval method and handle in legacy skin --- osu.Game/Skinning/LegacySkin.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index eae3b69233..e827dca232 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -16,6 +16,7 @@ using osu.Framework.IO.Stores; using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.IO; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; using osuTK.Graphics; @@ -119,6 +120,9 @@ namespace osu.Game.Skinning break; + case SkinComboColourLookup comboColour: + return SkinUtils.As(GetComboColour(Configuration, comboColour.ColourIndex, comboColour.Combo)); + case SkinCustomColourLookup customColour: return SkinUtils.As(getCustomColour(Configuration, customColour.Lookup.ToString())); @@ -276,6 +280,18 @@ namespace osu.Game.Skinning return null; } + /// + /// Retrieves the correct combo colour for a given colour index and information on the combo. + /// + /// The source to retrieve the combo colours from. + /// The preferred index for retrieving the combo colour with. + /// Information on the combo whose using the returned colour. + protected virtual IBindable GetComboColour(IHasComboColours source, int colourIndex, IHasComboInformation combo) + { + var colour = source.ComboColours?[colourIndex % source.ComboColours.Count]; + return colour.HasValue ? new Bindable(colour.Value) : null; + } + private IBindable getCustomColour(IHasCustomColours source, string lookup) => source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; From 78794935b47f95648ac620378ddfe11108933e59 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 07:11:45 +0300 Subject: [PATCH 0272/1621] Handle combo colour lookups in other skins --- .../Gameplay/TestSceneHitObjectAccentColour.cs | 10 ++-------- osu.Game/Skinning/DefaultSkin.cs | 11 +++++++++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index 883791c35c..d08d08390b 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -127,14 +127,8 @@ namespace osu.Game.Tests.Gameplay { switch (lookup) { - case GlobalSkinColours global: - switch (global) - { - case GlobalSkinColours.ComboColours: - return SkinUtils.As(new Bindable>(ComboColours)); - } - - break; + case SkinComboColourLookup comboColour: + return SkinUtils.As(new Bindable(ComboColours[comboColour.ColourIndex % ComboColours.Count])); } throw new NotImplementedException(); diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 0b3f5f3cde..a2d05a918c 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; +using osu.Game.Beatmaps.Formats; using osuTK.Graphics; namespace osu.Game.Skinning @@ -28,10 +29,10 @@ namespace osu.Game.Skinning public override IBindable GetConfig(TLookup lookup) { + // todo: this code is pulled from LegacySkin and should not exist. + // will likely change based on how databased storage of skin configuration goes. switch (lookup) { - // todo: this code is pulled from LegacySkin and should not exist. - // will likely change based on how databased storage of skin configuration goes. case GlobalSkinColours global: switch (global) { @@ -40,9 +41,15 @@ namespace osu.Game.Skinning } break; + + case SkinComboColourLookup comboColour: + return SkinUtils.As(new Bindable(getComboColour(Configuration, comboColour.ColourIndex))); } return null; } + + private static Color4 getComboColour(IHasComboColours source, int colourIndex) + => source.ComboColours[colourIndex % source.ComboColours.Count]; } } From eeeb001d62279df9a5430b4345844dccc042acfd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 07:17:27 +0300 Subject: [PATCH 0273/1621] Refactor combo colour retrieval logic to request skin lookups instead --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 3 ++- .../Objects/PalpableCatchHitObject.cs | 4 ++-- .../Objects/Drawables/DrawableHitObject.cs | 3 +-- .../Objects/Types/IHasComboInformation.cs | 23 +++++++++++++------ .../Timeline/TimelineHitObjectBlueprint.cs | 4 +--- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index 178306b3bc..e5a36d08db 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -9,6 +9,7 @@ using osu.Game.Audio; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Skinning; using osu.Game.Utils; using osuTK.Graphics; @@ -31,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Objects } // override any external colour changes with banananana - Color4 IHasComboInformation.GetComboColour(IReadOnlyList comboColours) => getBananaColour(); + Color4 IHasComboInformation.GetComboColour(ISkin skin) => getBananaColour(); private Color4 getBananaColour() { diff --git a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs index 0cd3af01df..24c12343e5 100644 --- a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.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.Collections.Generic; using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects @@ -43,6 +43,6 @@ namespace osu.Game.Rulesets.Catch.Objects } } - Color4 IHasComboInformation.GetComboColour(IReadOnlyList comboColours) => comboColours[(IndexInBeatmap + 1) % comboColours.Count]; + Color4 IHasComboInformation.GetComboColour(ISkin skin) => IHasComboInformation.GetSkinComboColour(this, skin, IndexInBeatmap + 1); } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 86c733c392..63ae2e286f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -509,8 +509,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { if (!(HitObject is IHasComboInformation combo)) return; - var comboColours = CurrentSkin.GetConfig>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty(); - AccentColour.Value = combo.GetComboColour(comboColours); + AccentColour.Value = combo.GetComboColour(CurrentSkin); } /// diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs index 4f66802079..03e6f76cca 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using JetBrains.Annotations; using osu.Framework.Bindables; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Types @@ -40,11 +39,21 @@ namespace osu.Game.Rulesets.Objects.Types bool LastInCombo { get; set; } /// - /// Retrieves the colour of the combo described by this object from a set of possible combo colours. - /// Defaults to using to decide the colour. + /// Retrieves the colour of the combo described by this object. /// - /// A list of possible combo colours provided by the beatmap or skin. - /// The colour of the combo described by this object. - Color4 GetComboColour([NotNull] IReadOnlyList comboColours) => comboColours.Count > 0 ? comboColours[ComboIndex % comboColours.Count] : Color4.White; + /// The skin to retrieve the combo colour from, if wanted. + Color4 GetComboColour(ISkin skin) => GetSkinComboColour(this, skin, ComboIndex); + + /// + /// Retrieves the colour of the combo described by a given object from a given skin. + /// + /// The combo information, should be this. + /// The skin to retrieve the combo colour from. + /// The index to retrieve the combo colour with. + /// + protected static Color4 GetSkinComboColour(IHasComboInformation combo, ISkin skin, int comboIndex) + { + return skin.GetConfig(new SkinComboColourLookup(comboIndex, combo))?.Value ?? Color4.White; + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index dbe689be2f..67a28c4fa0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -139,8 +138,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (!(Item is IHasComboInformation combo)) return; - var comboColours = skin.GetConfig>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty(); - var comboColour = combo.GetComboColour(comboColours); + var comboColour = combo.GetComboColour(skin); if (IsSelected) { From 946abfbb83d89caea8e7273901a1e1a020824813 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Wed, 12 May 2021 18:11:50 +0200 Subject: [PATCH 0274/1621] Rework settings; Add seed to ScorePanel; Apply requested changes from @bdach --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 200 +++++++----------- osu.Game/Rulesets/Mods/ModRandom.cs | 1 + osu.Game/Scoring/ScoreInfo.cs | 18 ++ .../Expanded/ExpandedPanelMiddleContent.cs | 47 +++- osu.Game/Screens/Ranking/ScorePanel.cs | 8 +- 5 files changed, 152 insertions(+), 122 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 9a6127cdad..2d7c52d535 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -35,68 +35,46 @@ namespace osu.Game.Rulesets.Osu.Mods private const byte border_distance_x = 192; private const byte border_distance_y = 144; - private static readonly Bindable seed = new Bindable + [SettingSource("Custom seed", "Use a custom seed instead of a random one", SettingControlType = typeof(OsuModRandomSettingsControl))] + public Bindable CustomSeed { get; } = new Bindable { - Default = -1 + Default = null, + Value = null }; - private static readonly BindableBool random_seed = new BindableBool + public void ApplyToBeatmap(IBeatmap beatmap) { - Value = true, - Default = true - }; - - [SettingSource("Random seed", "Generate a random seed for the beatmap generation")] - public BindableBool RandomSeed => random_seed; - - [SettingSource("Seed", "Seed for the random beatmap generation", SettingControlType = typeof(OsuModRandomSettingsControl))] - public Bindable Seed => seed; - - internal static bool CustomSeedDisabled => random_seed.Value; - - public OsuModRandom() - { - if (seed.Default != -1) + if (!(beatmap is OsuBeatmap osuBeatmap)) return; - var random = RNG.Next(); - seed.Value = random; - seed.Default = random; - seed.BindValueChanged(e => seed.Default = e.NewValue); - } + var seed = RNG.Next(); - public void ApplyToBeatmap(IBeatmap iBeatmap) - { - if (!(iBeatmap is OsuBeatmap beatmap)) - return; + if (CustomSeed.Value != null) + seed = (int)CustomSeed.Value; - if (RandomSeed.Value) - seed.Value = RNG.Next(); + Seed = seed; - var rng = new Random(seed.Value); + var rng = new Random(seed); - // Absolute angle - float prevAngleRad = 0; - - // Absolute positions - Vector2 prevPosUnchanged = beatmap.HitObjects[0].Position; - Vector2 prevPosChanged = beatmap.HitObjects[0].Position; + var prevObjectInfo = new HitObjectInfo + { + AngleRad = 0, + PosUnchanged = osuBeatmap.HitObjects[0].Position, + PosChanged = osuBeatmap.HitObjects[0].Position + }; // rateOfChangeMultiplier changes every i iterations to prevent shaky-line-shaped streams byte i = 3; float rateOfChangeMultiplier = 0; - foreach (var beatmapHitObject in beatmap.HitObjects) + foreach (var currentHitObject in osuBeatmap.HitObjects) { - if (!(beatmapHitObject is OsuHitObject hitObject)) - return; - - // posUnchanged: position from the original beatmap (not randomised) - var posUnchanged = hitObject.EndPosition; - var posChanged = Vector2.Zero; - - // Angle of the vector pointing from the last to the current hit object - float angleRad = 0; + var currentObjectInfo = new HitObjectInfo + { + AngleRad = 0, + PosUnchanged = currentHitObject.EndPosition, + PosChanged = Vector2.Zero + }; if (i >= 3) { @@ -104,24 +82,23 @@ namespace osu.Game.Rulesets.Osu.Mods rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; } - if (hitObject is HitCircle circle) + if (currentHitObject is HitCircle circle) { - var distanceToPrev = Vector2.Distance(posUnchanged, prevPosUnchanged); + var distanceToPrev = Vector2.Distance(currentObjectInfo.PosUnchanged, prevObjectInfo.PosUnchanged); - circle.Position = posChanged = getRandomisedPosition( + getObjectInfo( rateOfChangeMultiplier, - prevPosChanged, - prevAngleRad, + prevObjectInfo, distanceToPrev, - out angleRad + ref currentObjectInfo ); + + circle.Position = currentObjectInfo.PosChanged; } // TODO: Implement slider position randomisation - prevAngleRad = angleRad; - prevPosUnchanged = posUnchanged; - prevPosChanged = posChanged; + prevObjectInfo = currentObjectInfo; i++; } } @@ -130,12 +107,11 @@ namespace osu.Game.Rulesets.Osu.Mods /// Returns the final position of the hit object /// /// Final position of the hit object - private Vector2 getRandomisedPosition( + private void getObjectInfo( float rateOfChangeMultiplier, - Vector2 prevPosChanged, - float prevAngleRad, + HitObjectInfo prevObjectInfo, float distanceToPrev, - out float newAngle) + ref HitObjectInfo currentObjectInfo) { // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) // is proportional to the distance between the last and the current hit object @@ -143,19 +119,19 @@ namespace osu.Game.Rulesets.Osu.Mods var maxDistance = OsuPlayfield.BASE_SIZE.LengthFast; var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * distanceToPrev / maxDistance; - newAngle = (float)randomAngleRad + prevAngleRad; - if (newAngle < 0) - newAngle += 2 * (float)Math.PI; + currentObjectInfo.AngleRad = (float)randomAngleRad + prevObjectInfo.AngleRad; + if (currentObjectInfo.AngleRad < 0) + currentObjectInfo.AngleRad += 2 * (float)Math.PI; var posRelativeToPrev = new Vector2( - distanceToPrev * (float)Math.Cos(newAngle), - distanceToPrev * (float)Math.Sin(newAngle) + distanceToPrev * (float)Math.Cos(currentObjectInfo.AngleRad), + distanceToPrev * (float)Math.Sin(currentObjectInfo.AngleRad) ); - posRelativeToPrev = getRotatedVector(prevPosChanged, posRelativeToPrev); + posRelativeToPrev = getRotatedVector(prevObjectInfo.PosChanged, posRelativeToPrev); - newAngle = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); - var position = Vector2.Add(prevPosChanged, posRelativeToPrev); + currentObjectInfo.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); + var position = Vector2.Add(prevObjectInfo.PosChanged, posRelativeToPrev); // Move hit objects back into the playfield if they are outside of it, // which would sometimes happen during big jumps otherwise. @@ -169,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Mods else if (position.Y > OsuPlayfield.BASE_SIZE.Y) position.Y = OsuPlayfield.BASE_SIZE.Y; - return position; + currentObjectInfo.PosChanged = position; } /// @@ -214,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Mods return rotateVectorTowardsVector( posRelativeToPrev, Vector2.Subtract(playfieldMiddle, prevPosChanged), - relativeRotationDistance + relativeRotationDistance / 2 ); } @@ -230,10 +206,6 @@ namespace osu.Game.Rulesets.Osu.Mods var initialAngleRad = Math.Atan2(initial.Y, initial.X); var destAngleRad = Math.Atan2(destination.Y, destination.X); - // Divide by 2 to limit the max. angle to 90° - // (90° is enough to prevent the hit objects from leaving the playfield) - relativeDistance /= 2; - var diff = destAngleRad - initialAngleRad; while (diff < -Math.PI) @@ -246,46 +218,45 @@ namespace osu.Game.Rulesets.Osu.Mods diff -= 2 * Math.PI; } - var finalAngle = 0d; - - if (diff > 0) - { - finalAngle = initialAngleRad + relativeDistance * diff; - } - else if (diff < 0) - { - finalAngle = initialAngleRad + relativeDistance * diff; - } + var finalAngleRad = initialAngleRad + relativeDistance * diff; return new Vector2( - initial.Length * (float)Math.Cos(finalAngle), - initial.Length * (float)Math.Sin(finalAngle) + initial.Length * (float)Math.Cos(finalAngleRad), + initial.Length * (float)Math.Sin(finalAngleRad) ); } + + private struct HitObjectInfo + { + internal float AngleRad { get; set; } + internal Vector2 PosUnchanged { get; set; } + internal Vector2 PosChanged { get; set; } + } } - public class OsuModRandomSettingsControl : SettingsItem + public class OsuModRandomSettingsControl : SettingsItem { - [Resolved] - private static GameHost host { get; set; } - - [BackgroundDependencyLoader] - private void load(GameHost gameHost) => host = gameHost; - protected override Drawable CreateControl() => new SeedControl { RelativeSizeAxes = Axes.X, Margin = new MarginPadding { Top = 5 } }; - private sealed class SeedControl : CompositeDrawable, IHasCurrentValue + private sealed class SeedControl : CompositeDrawable, IHasCurrentValue { - private readonly BindableWithCurrent current = new BindableWithCurrent(); + [Resolved] + private GameHost host { get; set; } - public Bindable Current + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current { get => current; - set => Scheduler.Add(() => current.Current = value); + set + { + current.Current = value; + seedNumberBox.Text = value.Value.ToString(); + } } private readonly OsuNumberBox seedNumberBox; @@ -324,40 +295,29 @@ namespace osu.Game.Rulesets.Osu.Mods { RelativeSizeAxes = Axes.Both, Height = 1, - Text = "Copy", - Action = copySeedToClipboard + Text = "Paste", + Action = () => seedNumberBox.Text = host.GetClipboard().GetText() } } } } }; - seedNumberBox.Current.BindValueChanged(onTextBoxValueChanged); + seedNumberBox.Current.BindValueChanged(e => + { + int? value = null; + + if (int.TryParse(e.NewValue, out var intVal)) + value = intVal; + + current.Value = value; + }); } - private void onTextBoxValueChanged(ValueChangedEvent e) - { - string seed = e.NewValue; - - while (!string.IsNullOrEmpty(seed) && !int.TryParse(seed, out _)) - seed = seed[..^1]; - - if (!int.TryParse(seed, out var intVal)) - intVal = 0; - - current.Value = intVal; - } - - private void copySeedToClipboard() => host.GetClipboard().SetText(seedNumberBox.Text); - protected override void Update() { - seedNumberBox.ReadOnly = OsuModRandom.CustomSeedDisabled; - - if (seedNumberBox.HasFocus) - return; - - seedNumberBox.Text = current.Current.Value.ToString(); + if (current.Value == null) + seedNumberBox.Text = current.Current.Value.ToString(); } } } diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index da55ab3fbf..382792f75c 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -13,5 +13,6 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.Conversion; public override IconUsage? Icon => OsuIcon.Dice; public override double ScoreMultiplier => 1; + public int? Seed { get; protected set; } } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index a6faaf6379..b584d24370 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -88,6 +88,24 @@ namespace osu.Game.Scoring } } + public bool ContainsModOfType(out T mod) + { + if (mods != null) + { + foreach (var currentMod in mods) + { + if (!(currentMod is T modOfType)) + continue; + + mod = modOfType; + return true; + } + } + + mod = default; + return false; + } + // Used for API serialisation/deserialisation. [JsonProperty("mods")] [NotMapped] diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 6a6b39b61c..4d81290a75 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -7,11 +7,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; +using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; @@ -55,6 +57,9 @@ namespace osu.Game.Screens.Ranking.Expanded Padding = new MarginPadding(padding); } + [Resolved] + private GameHost host { get; set; } + [BackgroundDependencyLoader] private void load(BeatmapDifficultyCache beatmapDifficultyCache) { @@ -224,7 +229,47 @@ namespace osu.Game.Screens.Ranking.Expanded } } } - } + }.With(t => + { + if (!score.ContainsModOfType(out var mod) || mod.Seed == null) + return; + + t.Add(new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding + { + Top = 3f, + Right = 5f + }, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Medium), + Text = $"Seed: {mod.Seed}" + }, + new TriangleButton + { + RelativeSizeAxes = Axes.Both, + Height = 1.2f, + Width = 0.5f, + Text = "Copy", + Action = () => host.GetClipboard().SetText(mod.Seed.ToString()) + } + } + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + } + }); + }) } }, new OsuSpriteText diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index df710e4eb8..33b06571fe 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Contracted; using osu.Game.Screens.Ranking.Expanded; @@ -206,7 +207,12 @@ namespace osu.Game.Screens.Ranking switch (state) { case PanelState.Expanded: - Size = new Vector2(EXPANDED_WIDTH, expanded_height); + var height = expanded_height; + + if (Score.ContainsModOfType(out var mod) && mod.Seed != null) + height += 20f; + + Size = new Vector2(EXPANDED_WIDTH, height); topLayerBackground.FadeColour(expanded_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint); From a9d5211e81593f30be9f7d7c5a91218a52b47a2a Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Fri, 14 May 2021 01:42:39 +0200 Subject: [PATCH 0275/1621] Remove seed from the ScorePanel and "Paste" button --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 15 ------ osu.Game/Rulesets/Mods/ModRandom.cs | 1 - osu.Game/Scoring/ScoreInfo.cs | 18 ------- .../Expanded/ExpandedPanelMiddleContent.cs | 47 +------------------ osu.Game/Screens/Ranking/ScorePanel.cs | 8 +--- 5 files changed, 2 insertions(+), 87 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 2d7c52d535..1b8f8ee7f5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -2,12 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Platform; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -52,8 +50,6 @@ namespace osu.Game.Rulesets.Osu.Mods if (CustomSeed.Value != null) seed = (int)CustomSeed.Value; - Seed = seed; - var rng = new Random(seed); var prevObjectInfo = new HitObjectInfo @@ -244,9 +240,6 @@ namespace osu.Game.Rulesets.Osu.Mods private sealed class SeedControl : CompositeDrawable, IHasCurrentValue { - [Resolved] - private GameHost host { get; set; } - private readonly BindableWithCurrent current = new BindableWithCurrent(); public Bindable Current @@ -289,14 +282,6 @@ namespace osu.Game.Rulesets.Osu.Mods { RelativeSizeAxes = Axes.X, CommitOnFocusLost = true - }, - null, - new TriangleButton - { - RelativeSizeAxes = Axes.Both, - Height = 1, - Text = "Paste", - Action = () => seedNumberBox.Text = host.GetClipboard().GetText() } } } diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index 382792f75c..da55ab3fbf 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -13,6 +13,5 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.Conversion; public override IconUsage? Icon => OsuIcon.Dice; public override double ScoreMultiplier => 1; - public int? Seed { get; protected set; } } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index b584d24370..a6faaf6379 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -88,24 +88,6 @@ namespace osu.Game.Scoring } } - public bool ContainsModOfType(out T mod) - { - if (mods != null) - { - foreach (var currentMod in mods) - { - if (!(currentMod is T modOfType)) - continue; - - mod = modOfType; - return true; - } - } - - mod = default; - return false; - } - // Used for API serialisation/deserialisation. [JsonProperty("mods")] [NotMapped] diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 4d81290a75..6a6b39b61c 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -7,13 +7,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; -using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; @@ -57,9 +55,6 @@ namespace osu.Game.Screens.Ranking.Expanded Padding = new MarginPadding(padding); } - [Resolved] - private GameHost host { get; set; } - [BackgroundDependencyLoader] private void load(BeatmapDifficultyCache beatmapDifficultyCache) { @@ -229,47 +224,7 @@ namespace osu.Game.Screens.Ranking.Expanded } } } - }.With(t => - { - if (!score.ContainsModOfType(out var mod) || mod.Seed == null) - return; - - t.Add(new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] - { - new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Margin = new MarginPadding - { - Top = 3f, - Right = 5f - }, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Medium), - Text = $"Seed: {mod.Seed}" - }, - new TriangleButton - { - RelativeSizeAxes = Axes.Both, - Height = 1.2f, - Width = 0.5f, - Text = "Copy", - Action = () => host.GetClipboard().SetText(mod.Seed.ToString()) - } - } - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - } - }); - }) + } } }, new OsuSpriteText diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 33b06571fe..df710e4eb8 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Contracted; using osu.Game.Screens.Ranking.Expanded; @@ -207,12 +206,7 @@ namespace osu.Game.Screens.Ranking switch (state) { case PanelState.Expanded: - var height = expanded_height; - - if (Score.ContainsModOfType(out var mod) && mod.Seed != null) - height += 20f; - - Size = new Vector2(EXPANDED_WIDTH, height); + Size = new Vector2(EXPANDED_WIDTH, expanded_height); topLayerBackground.FadeColour(expanded_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint); From ac04e8afa28a8bc5041176f0c59f8c77a109d2f2 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Fri, 14 May 2021 01:50:11 +0200 Subject: [PATCH 0276/1621] Change name of option "Custom seed" to "Seed" and set its value to the generated seed --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 1b8f8ee7f5..1141de58a2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -33,8 +33,8 @@ namespace osu.Game.Rulesets.Osu.Mods private const byte border_distance_x = 192; private const byte border_distance_y = 144; - [SettingSource("Custom seed", "Use a custom seed instead of a random one", SettingControlType = typeof(OsuModRandomSettingsControl))] - public Bindable CustomSeed { get; } = new Bindable + [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(OsuModRandomSettingsControl))] + public Bindable Seed { get; } = new Bindable { Default = null, Value = null @@ -45,12 +45,9 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(beatmap is OsuBeatmap osuBeatmap)) return; - var seed = RNG.Next(); + Seed.Value ??= RNG.Next(); - if (CustomSeed.Value != null) - seed = (int)CustomSeed.Value; - - var rng = new Random(seed); + var rng = new Random((int)Seed.Value); var prevObjectInfo = new HitObjectInfo { From dbc23187105279560e8a7bb26d206a450cf4604d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 14:13:35 +0900 Subject: [PATCH 0277/1621] Initial tidying up --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 29 +++++++++------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 1141de58a2..dc194aa464 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -45,6 +45,8 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(beatmap is OsuBeatmap osuBeatmap)) return; + var hitObjects = osuBeatmap.HitObjects; + Seed.Value ??= RNG.Next(); var rng = new Random((int)Seed.Value); @@ -52,30 +54,28 @@ namespace osu.Game.Rulesets.Osu.Mods var prevObjectInfo = new HitObjectInfo { AngleRad = 0, - PosUnchanged = osuBeatmap.HitObjects[0].Position, - PosChanged = osuBeatmap.HitObjects[0].Position + PosUnchanged = hitObjects[0].Position, + PosChanged = hitObjects[0].Position }; - // rateOfChangeMultiplier changes every i iterations to prevent shaky-line-shaped streams - byte i = 3; float rateOfChangeMultiplier = 0; - foreach (var currentHitObject in osuBeatmap.HitObjects) + for (int i = 0; i < hitObjects.Count; i++) { + var h = hitObjects[i]; + var currentObjectInfo = new HitObjectInfo { AngleRad = 0, - PosUnchanged = currentHitObject.EndPosition, + PosUnchanged = h.EndPosition, PosChanged = Vector2.Zero }; - if (i >= 3) - { - i = 0; + // rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams + if (i % 3 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; - } - if (currentHitObject is HitCircle circle) + if (h is HitCircle circle) { var distanceToPrev = Vector2.Distance(currentObjectInfo.PosUnchanged, prevObjectInfo.PosUnchanged); @@ -92,7 +92,6 @@ namespace osu.Game.Rulesets.Osu.Mods // TODO: Implement slider position randomisation prevObjectInfo = currentObjectInfo; - i++; } } @@ -100,11 +99,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// Returns the final position of the hit object /// /// Final position of the hit object - private void getObjectInfo( - float rateOfChangeMultiplier, - HitObjectInfo prevObjectInfo, - float distanceToPrev, - ref HitObjectInfo currentObjectInfo) + private void getObjectInfo(float rateOfChangeMultiplier, HitObjectInfo prevObjectInfo, float distanceToPrev, ref HitObjectInfo currentObjectInfo) { // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) // is proportional to the distance between the last and the current hit object From 3fa6a0413b8a3a3223432301e5f35057177e81dc Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Fri, 14 May 2021 23:04:09 +0200 Subject: [PATCH 0278/1621] Add slider position randomisation --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 90 ++++++++++++++++------ 1 file changed, 68 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index dc194aa464..90036e6839 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -53,9 +53,10 @@ namespace osu.Game.Rulesets.Osu.Mods var prevObjectInfo = new HitObjectInfo { - AngleRad = 0, - PosUnchanged = hitObjects[0].Position, - PosChanged = hitObjects[0].Position + StartPosUnchanged = hitObjects[0].Position, + EndPosUnchanged = hitObjects[0].EndPosition, + StartPosChanged = hitObjects[0].Position, + EndPosChanged = hitObjects[0].EndPosition }; float rateOfChangeMultiplier = 0; @@ -66,31 +67,51 @@ namespace osu.Game.Rulesets.Osu.Mods var currentObjectInfo = new HitObjectInfo { - AngleRad = 0, - PosUnchanged = h.EndPosition, - PosChanged = Vector2.Zero + StartPosUnchanged = h.Position, + EndPosUnchanged = h.EndPosition, + StartPosChanged = Vector2.Zero, + EndPosChanged = Vector2.Zero }; // rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams if (i % 3 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; - if (h is HitCircle circle) + var distanceToPrev = Vector2.Distance(prevObjectInfo.EndPosUnchanged, currentObjectInfo.StartPosUnchanged); + + switch (h) { - var distanceToPrev = Vector2.Distance(currentObjectInfo.PosUnchanged, prevObjectInfo.PosUnchanged); + case HitCircle circle: + getObjectInfo( + rateOfChangeMultiplier, + prevObjectInfo, + distanceToPrev, + ref currentObjectInfo + ); - getObjectInfo( - rateOfChangeMultiplier, - prevObjectInfo, - distanceToPrev, - ref currentObjectInfo - ); + circle.Position = currentObjectInfo.StartPosChanged; + currentObjectInfo.EndPosChanged = currentObjectInfo.StartPosChanged; + break; - circle.Position = currentObjectInfo.PosChanged; + case Slider slider: + currentObjectInfo.EndPosUnchanged = slider.EndPosition; + + currentObjectInfo.EndPosUnchanged = slider.TailCircle.Position; + + getObjectInfo( + rateOfChangeMultiplier, + prevObjectInfo, + distanceToPrev, + ref currentObjectInfo + ); + + slider.Position = currentObjectInfo.StartPosChanged; + currentObjectInfo.EndPosChanged = slider.TailCircle.Position; + + moveSliderIntoPlayfield(ref slider, ref currentObjectInfo); + break; } - // TODO: Implement slider position randomisation - prevObjectInfo = currentObjectInfo; } } @@ -116,10 +137,10 @@ namespace osu.Game.Rulesets.Osu.Mods distanceToPrev * (float)Math.Sin(currentObjectInfo.AngleRad) ); - posRelativeToPrev = getRotatedVector(prevObjectInfo.PosChanged, posRelativeToPrev); + posRelativeToPrev = getRotatedVector(prevObjectInfo.EndPosChanged, posRelativeToPrev); currentObjectInfo.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); - var position = Vector2.Add(prevObjectInfo.PosChanged, posRelativeToPrev); + var position = Vector2.Add(prevObjectInfo.EndPosChanged, posRelativeToPrev); // Move hit objects back into the playfield if they are outside of it, // which would sometimes happen during big jumps otherwise. @@ -133,7 +154,30 @@ namespace osu.Game.Rulesets.Osu.Mods else if (position.Y > OsuPlayfield.BASE_SIZE.Y) position.Y = OsuPlayfield.BASE_SIZE.Y; - currentObjectInfo.PosChanged = position; + currentObjectInfo.StartPosChanged = position; + } + + private void moveSliderIntoPlayfield(ref Slider slider, ref HitObjectInfo currentObjectInfo) + { + foreach (var controlPoint in slider.Path.ControlPoints) + { + // Position of controlPoint relative to slider.Position + var pos = controlPoint.Position.Value; + + var playfieldSize = OsuPlayfield.BASE_SIZE; + + if (pos.X + slider.Position.X < 0) + slider.Position = new Vector2(-pos.X, slider.Position.Y); + else if (pos.X + slider.Position.X > playfieldSize.X) + slider.Position = new Vector2(playfieldSize.X - pos.X, slider.Position.Y); + + if (pos.Y + slider.Position.Y < 0) + slider.Position = new Vector2(slider.Position.X, -pos.Y); + else if (pos.Y + slider.Position.Y > playfieldSize.Y) + slider.Position = new Vector2(slider.Position.X, playfieldSize.Y - pos.Y); + } + + currentObjectInfo.EndPosChanged = slider.TailCircle.Position; } /// @@ -217,8 +261,10 @@ namespace osu.Game.Rulesets.Osu.Mods private struct HitObjectInfo { internal float AngleRad { get; set; } - internal Vector2 PosUnchanged { get; set; } - internal Vector2 PosChanged { get; set; } + internal Vector2 StartPosUnchanged { get; set; } + internal Vector2 EndPosUnchanged { get; set; } + internal Vector2 StartPosChanged { get; set; } + internal Vector2 EndPosChanged { get; set; } } } From 878182fbdf480348d1114f0bc1e9f47c6141552c Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 15 May 2021 02:07:24 +0200 Subject: [PATCH 0279/1621] Fix slider ticks not being shifted along with their parent sliders --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 90036e6839..d06e807500 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -91,6 +92,7 @@ namespace osu.Game.Rulesets.Osu.Mods circle.Position = currentObjectInfo.StartPosChanged; currentObjectInfo.EndPosChanged = currentObjectInfo.StartPosChanged; + break; case Slider slider: @@ -109,6 +111,12 @@ namespace osu.Game.Rulesets.Osu.Mods currentObjectInfo.EndPosChanged = slider.TailCircle.Position; moveSliderIntoPlayfield(ref slider, ref currentObjectInfo); + + var sliderShift = Vector2.Subtract(slider.Position, currentObjectInfo.StartPosUnchanged); + + foreach (var tick in slider.NestedHitObjects.OfType()) + tick.Position = Vector2.Add(tick.Position, sliderShift); + break; } From fe099be443b7ce7f2552cfa9958752197a43ebea Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 19 May 2021 22:25:56 +0800 Subject: [PATCH 0280/1621] Implemented osu target mod --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 367 ++++++++++++++++++++- 1 file changed, 366 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 2464308347..667fc91142 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -1,13 +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 System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModTarget : Mod + public class OsuModTarget : ModWithVisibilityAdjustment, IApplicableToDrawableRuleset, + IApplicableToHealthProcessor, IApplicableToDifficulty { public override string Name => "Target"; public override string Acronym => "TP"; @@ -15,5 +36,349 @@ namespace osu.Game.Rulesets.Osu.Mods public override IconUsage? Icon => OsuIcon.ModTarget; public override string Description => @"Practice keeping up with the beat of the song."; public override double ScoreMultiplier => 1; + + public void ApplyToHealthProcessor(HealthProcessor healthProcessor) + { + // Sudden death + healthProcessor.FailConditions += (_, result) + => result.Type.AffectsCombo() + && !result.IsHit; + } + + // Maximum distance to jump + public const float MAX_DISTANCE = 250f; + + public override void ApplyToBeatmap(IBeatmap beatmap) + { + base.ApplyToBeatmap(beatmap); + + var osuBeatmap = (OsuBeatmap)beatmap; + var origHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); + + var hitObjects = new List(); + + // Only place circles between startTime and endTime + var startTime = origHitObjects.First().StartTime; + double endTime; + var endObj = origHitObjects.Last(); + switch (endObj) + { + case Slider slider: + endTime = slider.EndTime; + break; + case Spinner spinner: + endTime = spinner.EndTime; + break; + default: + endTime = endObj.StartTime; + break; + } + + var comboStarts = origHitObjects.Where(x => x.NewCombo).Select(x => x.StartTime).ToList(); + + TimingControlPoint currentTimingPoint = osuBeatmap.ControlPointInfo.TimingPointAt(startTime); + var currentTime = currentTimingPoint.Time; + int currentCombo = -1; + IList lastSamples = null; + + float direction = MathHelper.TwoPi * RNG.NextSingle(); + float distance = 40f; + + while (!Precision.AlmostBigger(currentTime, endTime)) + { + // Place a circle + + // Don't place any circles before the start of the first hit object of the map + // Don't place any circles from the start of break time to the start of the first hit object after the break + if (!Precision.AlmostBigger(startTime, currentTime) + && !osuBeatmap.Breaks.Any(x => Precision.AlmostBigger(currentTime, x.StartTime) + && Precision.AlmostBigger(origHitObjects.First(y => Precision.AlmostBigger(y.StartTime, x.EndTime)).StartTime, currentTime))) + { + var newCircle = new HitCircle(); + newCircle.ApplyDefaults(osuBeatmap.ControlPointInfo, osuBeatmap.BeatmapInfo.BaseDifficulty); + newCircle.StartTime = currentTime; + + // Determine circle position + if (hitObjects.Count == 0) + { + newCircle.Position = new Vector2(RNG.NextSingle(OsuPlayfield.BASE_SIZE.X), RNG.NextSingle(OsuPlayfield.BASE_SIZE.Y)); + } + else + { + var relativePos = new Vector2( + distance * (float)Math.Cos(direction), + distance * (float)Math.Sin(direction) + ); + relativePos = getRotatedVector(hitObjects.Last().Position, relativePos); + direction = (float)Math.Atan2(relativePos.Y, relativePos.X); + + var newPosition = Vector2.Add(hitObjects.Last().Position, relativePos); + + if (newPosition.Y < 0) + newPosition.Y = 0; + else if (newPosition.Y > OsuPlayfield.BASE_SIZE.Y) + newPosition.Y = OsuPlayfield.BASE_SIZE.Y; + if (newPosition.X < 0) + newPosition.X = 0; + else if (newPosition.X > OsuPlayfield.BASE_SIZE.X) + newPosition.X = OsuPlayfield.BASE_SIZE.X; + + newCircle.Position = newPosition; + // Add a random nudge to the direction + direction += distance / MAX_DISTANCE * (RNG.NextSingle() * MathHelper.TwoPi - MathHelper.Pi); + } + + // Determine samples to use + var samples = getSamplesAtTime(origHitObjects, currentTime); + if (samples == null) + { + if (lastSamples != null) + newCircle.Samples = lastSamples; + } + else + { + newCircle.Samples = samples; + lastSamples = samples; + } + + // Determine combo + if (comboStarts.Count > currentCombo + 1 && !Precision.AlmostBigger(comboStarts[currentCombo + 1], currentTime)) + { + currentCombo++; + newCircle.NewCombo = true; + newCircle.IndexInCurrentCombo = 0; + if (hitObjects.Count > 0) + hitObjects.Last().LastInCombo = true; + + // Increase distance for every combo + distance = Math.Min(distance * 1.05f, MAX_DISTANCE); + // Randomize direction as well + direction = MathHelper.TwoPi * RNG.NextSingle(); + } + else + { + if (hitObjects.Count > 0) + newCircle.IndexInCurrentCombo = hitObjects.Last().IndexInCurrentCombo + 1; + } + newCircle.ComboIndex = currentCombo; + + // Remove the last circle if it is too close in time to this one + if (hitObjects.Count > 0 + && Precision.AlmostBigger(currentTimingPoint.BeatLength / 2, newCircle.StartTime - hitObjects.Last().StartTime)) + { + hitObjects.RemoveAt(hitObjects.Count - 1); + } + + hitObjects.Add(newCircle); + } + + // Advance to the next beat and check for timing point changes + + currentTime += currentTimingPoint.BeatLength; + + var newTimingPoint = osuBeatmap.ControlPointInfo.TimingPointAt(currentTime); + if (newTimingPoint != currentTimingPoint) + { + currentTimingPoint = newTimingPoint; + currentTime = currentTimingPoint.Time; + } + } + + osuBeatmap.HitObjects = hitObjects; + } + + /// + /// Get samples (if any) for a specific point in time. + /// + /// + /// Samples will be returned if a hit circle or a slider node exists at that point of time. + /// + /// The list of hit objects in a beatmap, ordered by StartTime + /// The point in time to get samples for + /// Hit samples + private IList getSamplesAtTime(List hitObjects, double time) + { + var sampleObj = hitObjects.FirstOrDefault(x => + { + if (Precision.AlmostEquals(time, x.StartTime)) return true; + if (x is Slider slider + && Precision.AlmostBigger(time, slider.StartTime) + && Precision.AlmostBigger(slider.EndTime, time)) + { + if (Precision.AlmostEquals((time - slider.StartTime) % slider.SpanDuration, 0)) + { + return true; + } + } + return false; + }); + if (sampleObj == null) return null; + + IList samples = null; + if (sampleObj is Slider slider) + { + samples = slider.NodeSamples[(int)Math.Round((time - slider.StartTime) % slider.SpanDuration)]; + } + else + { + samples = sampleObj.Samples; + } + return samples; + } + + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + + protected override void ApplyNormalVisibilityState(DrawableHitObject drawable, ArmedState state) + { + if (drawable is DrawableSpinner) + return; + + var h = (OsuHitObject)drawable.HitObject; + + // apply grow and fade effect + if (drawable is DrawableHitCircle circle) + { + using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) + { + drawable.ScaleTo(0.4f) + .Then().ScaleTo(1.6f, h.TimePreempt * 2); + drawable.FadeTo(0.5f) + .Then().Delay(h.TimeFadeIn).FadeTo(1f); + + // remove approach circles + circle.ApproachCircle.Hide(); + } + } + } + + public void ReadFromDifficulty(BeatmapDifficulty difficulty) + { + } + + public void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + // Decrease AR to increase preempt time + difficulty.ApproachRate *= 0.5f; + } + + // The distances from the hit objects to the borders of the playfield they start to "turn around" and curve towards the middle. + // The closer the hit objects draw to the border, the sharper the turn + private const byte border_distance_x = 192; + private const byte border_distance_y = 144; + + /// + /// Determines the position of the current hit object relative to the previous one. + /// + /// The position of the current hit object relative to the previous one + private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev) + { + var relativeRotationDistance = 0f; + var playfieldMiddle = Vector2.Divide(OsuPlayfield.BASE_SIZE, 2); + + if (prevPosChanged.X < playfieldMiddle.X) + { + relativeRotationDistance = Math.Max( + (border_distance_x - prevPosChanged.X) / border_distance_x, + relativeRotationDistance + ); + } + else + { + relativeRotationDistance = Math.Max( + (prevPosChanged.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x, + relativeRotationDistance + ); + } + + if (prevPosChanged.Y < playfieldMiddle.Y) + { + relativeRotationDistance = Math.Max( + (border_distance_y - prevPosChanged.Y) / border_distance_y, + relativeRotationDistance + ); + } + else + { + relativeRotationDistance = Math.Max( + (prevPosChanged.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y, + relativeRotationDistance + ); + } + + return rotateVectorTowardsVector( + posRelativeToPrev, + Vector2.Subtract(playfieldMiddle, prevPosChanged), + relativeRotationDistance / 2 + ); + } + + /// + /// Rotates vector "initial" towards vector "destinantion" + /// + /// Vector to rotate to "destination" + /// Vector "initial" should be rotated to + /// The angle the vector should be rotated relative to the difference between the angles of the the two vectors. + /// Resulting vector + private Vector2 rotateVectorTowardsVector(Vector2 initial, Vector2 destination, float relativeDistance) + { + var initialAngleRad = Math.Atan2(initial.Y, initial.X); + var destAngleRad = Math.Atan2(destination.Y, destination.X); + + var diff = destAngleRad - initialAngleRad; + + while (diff < -Math.PI) + { + diff += 2 * Math.PI; + } + + while (diff > Math.PI) + { + diff -= 2 * Math.PI; + } + + var finalAngleRad = initialAngleRad + relativeDistance * diff; + + return new Vector2( + initial.Length * (float)Math.Cos(finalAngleRad), + initial.Length * (float)Math.Sin(finalAngleRad) + ); + } + + // Background metronome + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + drawableRuleset.Overlays.Add(new TargetBeatContainer()); + } + + public class TargetBeatContainer : BeatSyncedContainer + { + private PausableSkinnableSound sample; + + public TargetBeatContainer() + { + Divisor = 1; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + sample = new PausableSkinnableSound(new SampleInfo("spinnerbonus")), // TODO: use another sample? + }; + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + if (!IsBeatSyncedWithTrack) return; + + sample?.Play(); + } + } } } From d20b5c2d5aa9865bb302fe17b4a541f237b436a1 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 20 May 2021 11:57:13 +0800 Subject: [PATCH 0281/1621] Refactored ApplyToBeatmap --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 202 +++++++++++---------- 1 file changed, 105 insertions(+), 97 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 667fc91142..412b7049ea 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -55,8 +55,6 @@ namespace osu.Game.Rulesets.Osu.Mods var osuBeatmap = (OsuBeatmap)beatmap; var origHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); - var hitObjects = new List(); - // Only place circles between startTime and endTime var startTime = origHitObjects.First().StartTime; double endTime; @@ -74,113 +72,122 @@ namespace osu.Game.Rulesets.Osu.Mods break; } - var comboStarts = origHitObjects.Where(x => x.NewCombo).Select(x => x.StartTime).ToList(); - - TimingControlPoint currentTimingPoint = osuBeatmap.ControlPointInfo.TimingPointAt(startTime); - var currentTime = currentTimingPoint.Time; - int currentCombo = -1; - IList lastSamples = null; - - float direction = MathHelper.TwoPi * RNG.NextSingle(); - float distance = 40f; - - while (!Precision.AlmostBigger(currentTime, endTime)) - { - // Place a circle - - // Don't place any circles before the start of the first hit object of the map - // Don't place any circles from the start of break time to the start of the first hit object after the break - if (!Precision.AlmostBigger(startTime, currentTime) - && !osuBeatmap.Breaks.Any(x => Precision.AlmostBigger(currentTime, x.StartTime) - && Precision.AlmostBigger(origHitObjects.First(y => Precision.AlmostBigger(y.StartTime, x.EndTime)).StartTime, currentTime))) + // Generate the beats + var beats = osuBeatmap.ControlPointInfo.TimingPoints + .Where(x => Precision.AlmostBigger(endTime, x.Time)) + .SelectMany(tp => + { + var tpBeats = new List(); + var currentTime = tp.Time; + while (Precision.AlmostBigger(endTime, currentTime) && osuBeatmap.ControlPointInfo.TimingPointAt(currentTime) == tp) + { + tpBeats.Add(currentTime); + currentTime += tp.BeatLength; + } + return tpBeats; + }) + // Remove beats that are before startTime + .Where(x => Precision.AlmostBigger(x, startTime)) + // Remove beats during breaks + .Where(x => !osuBeatmap.Breaks.Any(b => + Precision.AlmostBigger(x, b.StartTime) + && Precision.AlmostBigger(origHitObjects.First(y => Precision.AlmostBigger(y.StartTime, b.EndTime)).StartTime, x) + )) + .ToList(); + // Generate a hit circle for each beat + var hitObjects = beats + // Remove beats that are too close to the next one (e.g. due to timing point changes) + .Where((x, idx) => + { + if (idx == beats.Count - 1) return true; + if (Precision.AlmostBigger(osuBeatmap.ControlPointInfo.TimingPointAt(x).BeatLength / 2, beats[idx + 1] - x)) + return false; + return true; + }) + .Select(x => { var newCircle = new HitCircle(); newCircle.ApplyDefaults(osuBeatmap.ControlPointInfo, osuBeatmap.BeatmapInfo.BaseDifficulty); - newCircle.StartTime = currentTime; + newCircle.StartTime = x; + return (OsuHitObject)newCircle; + }).ToList(); - // Determine circle position - if (hitObjects.Count == 0) - { - newCircle.Position = new Vector2(RNG.NextSingle(OsuPlayfield.BASE_SIZE.X), RNG.NextSingle(OsuPlayfield.BASE_SIZE.Y)); - } - else - { - var relativePos = new Vector2( + // Add hit samples to the circles + for (int i = 0; i < hitObjects.Count; i++) + { + var x = hitObjects[i]; + var samples = getSamplesAtTime(origHitObjects, x.StartTime); + if (samples == null) + { + if (i > 0) + x.Samples = hitObjects[i - 1].Samples; + } + else + { + x.Samples = samples; + } + } + + // Process combo numbers + // First follow the combo indices in the original beatmap + hitObjects.ForEach(x => + { + var origObj = origHitObjects.FindLast(y => Precision.AlmostBigger(x.StartTime, y.StartTime)); + if (origObj == null) x.ComboIndex = 0; + else x.ComboIndex = origObj.ComboIndex; + }); + // Then reprocess them to ensure continuity in the combo indices and add indices in current combo + var combos = hitObjects.GroupBy(x => x.ComboIndex).ToList(); + for (int i = 0; i < combos.Count; i++) + { + var group = combos[i].ToList(); + group.First().NewCombo = true; + group.Last().LastInCombo = true; + + for (int j = 0; j < group.Count; j++) + { + var x = group[j]; + x.ComboIndex = i; + x.IndexInCurrentCombo = j; + } + } + + // Position all hit circles + var direction = MathHelper.TwoPi * RNG.NextSingle(); + for (int i = 0; i < hitObjects.Count; i++) + { + var x = hitObjects[i]; + if (i == 0) + { + x.Position = new Vector2(RNG.NextSingle(OsuPlayfield.BASE_SIZE.X), RNG.NextSingle(OsuPlayfield.BASE_SIZE.Y)); + } + else + { + var distance = Math.Min(MAX_DISTANCE, 40f * (float)Math.Pow(1.05, x.ComboIndex)); + var relativePos = new Vector2( distance * (float)Math.Cos(direction), distance * (float)Math.Sin(direction) ); - relativePos = getRotatedVector(hitObjects.Last().Position, relativePos); - direction = (float)Math.Atan2(relativePos.Y, relativePos.X); + relativePos = getRotatedVector(hitObjects[i - 1].Position, relativePos); + direction = (float)Math.Atan2(relativePos.Y, relativePos.X); - var newPosition = Vector2.Add(hitObjects.Last().Position, relativePos); + var newPosition = Vector2.Add(hitObjects[i - 1].Position, relativePos); - if (newPosition.Y < 0) - newPosition.Y = 0; - else if (newPosition.Y > OsuPlayfield.BASE_SIZE.Y) - newPosition.Y = OsuPlayfield.BASE_SIZE.Y; - if (newPosition.X < 0) - newPosition.X = 0; - else if (newPosition.X > OsuPlayfield.BASE_SIZE.X) - newPosition.X = OsuPlayfield.BASE_SIZE.X; + if (newPosition.Y < 0) + newPosition.Y = 0; + else if (newPosition.Y > OsuPlayfield.BASE_SIZE.Y) + newPosition.Y = OsuPlayfield.BASE_SIZE.Y; + if (newPosition.X < 0) + newPosition.X = 0; + else if (newPosition.X > OsuPlayfield.BASE_SIZE.X) + newPosition.X = OsuPlayfield.BASE_SIZE.X; - newCircle.Position = newPosition; - // Add a random nudge to the direction - direction += distance / MAX_DISTANCE * (RNG.NextSingle() * MathHelper.TwoPi - MathHelper.Pi); - } + x.Position = newPosition; - // Determine samples to use - var samples = getSamplesAtTime(origHitObjects, currentTime); - if (samples == null) - { - if (lastSamples != null) - newCircle.Samples = lastSamples; - } - else - { - newCircle.Samples = samples; - lastSamples = samples; - } - - // Determine combo - if (comboStarts.Count > currentCombo + 1 && !Precision.AlmostBigger(comboStarts[currentCombo + 1], currentTime)) - { - currentCombo++; - newCircle.NewCombo = true; - newCircle.IndexInCurrentCombo = 0; - if (hitObjects.Count > 0) - hitObjects.Last().LastInCombo = true; - - // Increase distance for every combo - distance = Math.Min(distance * 1.05f, MAX_DISTANCE); - // Randomize direction as well + if (x.LastInCombo) direction = MathHelper.TwoPi * RNG.NextSingle(); - } else - { - if (hitObjects.Count > 0) - newCircle.IndexInCurrentCombo = hitObjects.Last().IndexInCurrentCombo + 1; - } - newCircle.ComboIndex = currentCombo; - - // Remove the last circle if it is too close in time to this one - if (hitObjects.Count > 0 - && Precision.AlmostBigger(currentTimingPoint.BeatLength / 2, newCircle.StartTime - hitObjects.Last().StartTime)) - { - hitObjects.RemoveAt(hitObjects.Count - 1); - } - - hitObjects.Add(newCircle); - } - - // Advance to the next beat and check for timing point changes - - currentTime += currentTimingPoint.BeatLength; - - var newTimingPoint = osuBeatmap.ControlPointInfo.TimingPointAt(currentTime); - if (newTimingPoint != currentTimingPoint) - { - currentTimingPoint = newTimingPoint; - currentTime = currentTimingPoint.Time; + direction += distance / MAX_DISTANCE * (RNG.NextSingle() * MathHelper.TwoPi - MathHelper.Pi); } } @@ -242,6 +249,7 @@ namespace osu.Game.Rulesets.Osu.Mods { using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) { + // todo: this doesn't feel quite right yet drawable.ScaleTo(0.4f) .Then().ScaleTo(1.6f, h.TimePreempt * 2); drawable.FadeTo(0.5f) @@ -367,7 +375,7 @@ namespace osu.Game.Rulesets.Osu.Mods { InternalChildren = new Drawable[] { - sample = new PausableSkinnableSound(new SampleInfo("spinnerbonus")), // TODO: use another sample? + sample = new PausableSkinnableSound(new SampleInfo("spinnerbonus")), // todo: use another sample? }; } From 15e7cce264ed3c31cf03a29a7cadd241abbf030f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 22 May 2021 14:14:41 +0800 Subject: [PATCH 0282/1621] Added seed setting, fixed mod ignoring IncreaseFirstObjectVisibility --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 116 +++++++++++++++++++-- 1 file changed, 110 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 412b7049ea..3fe0161742 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -6,15 +6,23 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; @@ -37,6 +45,13 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Practice keeping up with the beat of the song."; public override double ScoreMultiplier => 1; + [SettingSource("Seed", "Seed for random circle placement", SettingControlType = typeof(OsuModTargetSettingsControl))] + public Bindable Seed { get; } = new Bindable + { + Default = null, + Value = null + }; + public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { // Sudden death @@ -50,7 +65,14 @@ namespace osu.Game.Rulesets.Osu.Mods public override void ApplyToBeatmap(IBeatmap beatmap) { - base.ApplyToBeatmap(beatmap); + Seed.Value ??= RNG.Next(); + + var rng = new Random(Seed.Value.GetValueOrDefault()); + + float nextSingle(float max = 1f) + { + return (float)(rng.NextDouble() * max); + } var osuBeatmap = (OsuBeatmap)beatmap; var origHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); @@ -153,13 +175,13 @@ namespace osu.Game.Rulesets.Osu.Mods } // Position all hit circles - var direction = MathHelper.TwoPi * RNG.NextSingle(); + var direction = MathHelper.TwoPi * nextSingle(); for (int i = 0; i < hitObjects.Count; i++) { var x = hitObjects[i]; if (i == 0) { - x.Position = new Vector2(RNG.NextSingle(OsuPlayfield.BASE_SIZE.X), RNG.NextSingle(OsuPlayfield.BASE_SIZE.Y)); + x.Position = new Vector2(nextSingle(OsuPlayfield.BASE_SIZE.X), nextSingle(OsuPlayfield.BASE_SIZE.Y)); } else { @@ -185,13 +207,15 @@ namespace osu.Game.Rulesets.Osu.Mods x.Position = newPosition; if (x.LastInCombo) - direction = MathHelper.TwoPi * RNG.NextSingle(); + direction = MathHelper.TwoPi * nextSingle(); else - direction += distance / MAX_DISTANCE * (RNG.NextSingle() * MathHelper.TwoPi - MathHelper.Pi); + direction += distance / MAX_DISTANCE * (nextSingle() * MathHelper.TwoPi - MathHelper.Pi); } } osuBeatmap.HitObjects = hitObjects; + + base.ApplyToBeatmap(beatmap); } /// @@ -233,7 +257,7 @@ namespace osu.Game.Rulesets.Osu.Mods return samples; } - protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + protected override void ApplyIncreasedVisibilityState(DrawableHitObject drawable, ArmedState state) { } @@ -389,4 +413,84 @@ namespace osu.Game.Rulesets.Osu.Mods } } } + + public class OsuModTargetSettingsControl : SettingsItem + { + protected override Drawable CreateControl() => new SeedControl + { + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 5 } + }; + + private sealed class SeedControl : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set + { + current.Current = value; + seedNumberBox.Text = value.Value.ToString(); + } + } + + private readonly OsuNumberBox seedNumberBox; + + public SeedControl() + { + AutoSizeAxes = Axes.Y; + + InternalChildren = new[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 120) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + seedNumberBox = new OsuNumberBox + { + RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true, + }, + new TriangleButton + { + Width = 120, + Text = "Randomise", + Action = () => current.Value = RNG.Next(), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + }, + } + } + } + }; + + seedNumberBox.Current.BindValueChanged(e => + { + if (int.TryParse(e.NewValue, out var intVal)) + current.Value = intVal; + else + current.Value = null; + }); + current.BindValueChanged(e => + { + seedNumberBox.Text = e.NewValue.ToString(); + }); + } + } + } } From 8dd3f11d28db64bb85256c4901f5ff8c26e47108 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 14:19:10 +0900 Subject: [PATCH 0283/1621] Tidy up struct and previous object handling --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 188 +++++++++++---------- 1 file changed, 97 insertions(+), 91 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index d06e807500..5214020a84 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -52,35 +52,32 @@ namespace osu.Game.Rulesets.Osu.Mods var rng = new Random((int)Seed.Value); - var prevObjectInfo = new HitObjectInfo + var prevObjectInfo = new RandomObjectInfo { - StartPosUnchanged = hitObjects[0].Position, - EndPosUnchanged = hitObjects[0].EndPosition, - StartPosChanged = hitObjects[0].Position, - EndPosChanged = hitObjects[0].EndPosition + PositionOriginal = hitObjects[0].Position, + EndPositionOriginal = hitObjects[0].EndPosition, + PositionRandomised = hitObjects[0].Position, + EndPositionRandomised = hitObjects[0].EndPosition }; float rateOfChangeMultiplier = 0; for (int i = 0; i < hitObjects.Count; i++) { - var h = hitObjects[i]; + var hitObject = hitObjects[i]; - var currentObjectInfo = new HitObjectInfo - { - StartPosUnchanged = h.Position, - EndPosUnchanged = h.EndPosition, - StartPosChanged = Vector2.Zero, - EndPosChanged = Vector2.Zero - }; + var currentObjectInfo = new RandomObjectInfo(hitObject); + + if (i == 0) + prevObjectInfo = currentObjectInfo; // rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams if (i % 3 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; - var distanceToPrev = Vector2.Distance(prevObjectInfo.EndPosUnchanged, currentObjectInfo.StartPosUnchanged); + var distanceToPrev = Vector2.Distance(prevObjectInfo.EndPositionOriginal, currentObjectInfo.PositionOriginal); - switch (h) + switch (hitObject) { case HitCircle circle: getObjectInfo( @@ -90,15 +87,15 @@ namespace osu.Game.Rulesets.Osu.Mods ref currentObjectInfo ); - circle.Position = currentObjectInfo.StartPosChanged; - currentObjectInfo.EndPosChanged = currentObjectInfo.StartPosChanged; + circle.Position = currentObjectInfo.PositionRandomised; + currentObjectInfo.EndPositionRandomised = currentObjectInfo.PositionRandomised; break; case Slider slider: - currentObjectInfo.EndPosUnchanged = slider.EndPosition; + currentObjectInfo.EndPositionOriginal = slider.EndPosition; - currentObjectInfo.EndPosUnchanged = slider.TailCircle.Position; + currentObjectInfo.EndPositionOriginal = slider.TailCircle.Position; getObjectInfo( rateOfChangeMultiplier, @@ -107,12 +104,12 @@ namespace osu.Game.Rulesets.Osu.Mods ref currentObjectInfo ); - slider.Position = currentObjectInfo.StartPosChanged; - currentObjectInfo.EndPosChanged = slider.TailCircle.Position; + slider.Position = currentObjectInfo.PositionRandomised; + currentObjectInfo.EndPositionRandomised = slider.TailCircle.Position; moveSliderIntoPlayfield(ref slider, ref currentObjectInfo); - var sliderShift = Vector2.Subtract(slider.Position, currentObjectInfo.StartPosUnchanged); + var sliderShift = Vector2.Subtract(slider.Position, currentObjectInfo.PositionOriginal); foreach (var tick in slider.NestedHitObjects.OfType()) tick.Position = Vector2.Add(tick.Position, sliderShift); @@ -128,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// Returns the final position of the hit object /// /// Final position of the hit object - private void getObjectInfo(float rateOfChangeMultiplier, HitObjectInfo prevObjectInfo, float distanceToPrev, ref HitObjectInfo currentObjectInfo) + private void getObjectInfo(float rateOfChangeMultiplier, RandomObjectInfo prevObjectInfo, float distanceToPrev, ref RandomObjectInfo currentObjectInfo) { // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) // is proportional to the distance between the last and the current hit object @@ -145,10 +142,10 @@ namespace osu.Game.Rulesets.Osu.Mods distanceToPrev * (float)Math.Sin(currentObjectInfo.AngleRad) ); - posRelativeToPrev = getRotatedVector(prevObjectInfo.EndPosChanged, posRelativeToPrev); + posRelativeToPrev = getRotatedVector(prevObjectInfo.EndPositionRandomised, posRelativeToPrev); currentObjectInfo.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); - var position = Vector2.Add(prevObjectInfo.EndPosChanged, posRelativeToPrev); + var position = Vector2.Add(prevObjectInfo.EndPositionRandomised, posRelativeToPrev); // Move hit objects back into the playfield if they are outside of it, // which would sometimes happen during big jumps otherwise. @@ -162,10 +159,10 @@ namespace osu.Game.Rulesets.Osu.Mods else if (position.Y > OsuPlayfield.BASE_SIZE.Y) position.Y = OsuPlayfield.BASE_SIZE.Y; - currentObjectInfo.StartPosChanged = position; + currentObjectInfo.PositionRandomised = position; } - private void moveSliderIntoPlayfield(ref Slider slider, ref HitObjectInfo currentObjectInfo) + private void moveSliderIntoPlayfield(ref Slider slider, ref RandomObjectInfo currentObjectInfo) { foreach (var controlPoint in slider.Path.ControlPoints) { @@ -185,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Mods slider.Position = new Vector2(slider.Position.X, playfieldSize.Y - pos.Y); } - currentObjectInfo.EndPosChanged = slider.TailCircle.Position; + currentObjectInfo.EndPositionRandomised = slider.TailCircle.Position; } /// @@ -266,89 +263,98 @@ namespace osu.Game.Rulesets.Osu.Mods ); } - private struct HitObjectInfo + private struct RandomObjectInfo { internal float AngleRad { get; set; } - internal Vector2 StartPosUnchanged { get; set; } - internal Vector2 EndPosUnchanged { get; set; } - internal Vector2 StartPosChanged { get; set; } - internal Vector2 EndPosChanged { get; set; } - } - } - public class OsuModRandomSettingsControl : SettingsItem - { - protected override Drawable CreateControl() => new SeedControl - { - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 5 } - }; + internal Vector2 PositionOriginal { get; set; } + internal Vector2 PositionRandomised { get; set; } - private sealed class SeedControl : CompositeDrawable, IHasCurrentValue - { - private readonly BindableWithCurrent current = new BindableWithCurrent(); + internal Vector2 EndPositionOriginal { get; set; } + internal Vector2 EndPositionRandomised { get; set; } - public Bindable Current + public RandomObjectInfo(OsuHitObject hitObject) { - get => current; - set - { - current.Current = value; - seedNumberBox.Text = value.Value.ToString(); - } + PositionRandomised = PositionOriginal = hitObject.Position; + EndPositionRandomised = EndPositionOriginal = hitObject.EndPosition; + AngleRad = 0; } + } - private readonly OsuNumberBox seedNumberBox; - - public SeedControl() + public class OsuModRandomSettingsControl : SettingsItem + { + protected override Drawable CreateControl() => new SeedControl { - AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 5 } + }; - InternalChildren = new[] + private sealed class SeedControl : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current { - new GridContainer + get => current; + set { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] + current.Current = value; + seedNumberBox.Text = value.Value.ToString(); + } + } + + private readonly OsuNumberBox seedNumberBox; + + public SeedControl() + { + AutoSizeAxes = Axes.Y; + + InternalChildren = new[] + { + new GridContainer { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), - new Dimension(GridSizeMode.Relative, 0.25f) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] { - seedNumberBox = new OsuNumberBox + new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(GridSizeMode.Relative, 0.25f) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true + seedNumberBox = new OsuNumberBox + { + RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true + } } } } - } - }; + }; - seedNumberBox.Current.BindValueChanged(e => + seedNumberBox.Current.BindValueChanged(e => + { + int? value = null; + + if (int.TryParse(e.NewValue, out var intVal)) + value = intVal; + + current.Value = value; + }); + } + + protected override void Update() { - int? value = null; - - if (int.TryParse(e.NewValue, out var intVal)) - value = intVal; - - current.Value = value; - }); - } - - protected override void Update() - { - if (current.Value == null) - seedNumberBox.Text = current.Current.Value.ToString(); + if (current.Value == null) + seedNumberBox.Text = current.Current.Value.ToString(); + } } } } From 88d7bc195dea687d508e6667b8dfd91c1162d1e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 14:24:56 +0900 Subject: [PATCH 0284/1621] Split out and clean up playfield sizing references --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 27 +++++++++++----------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 5214020a84..bbae746f67 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -29,10 +29,16 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "It never gets boring!"; public override bool Ranked => false; - // The distances from the hit objects to the borders of the playfield they start to "turn around" and curve towards the middle. + // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn - private const byte border_distance_x = 192; - private const byte border_distance_y = 144; + private const float playfield_edge_ratio = 0.375f; + + private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio; + private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio; + + private static readonly Vector2 playfield_middle = Vector2.Divide(OsuPlayfield.BASE_SIZE, 2); + + private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(OsuModRandomSettingsControl))] public Bindable Seed { get; } = new Bindable @@ -130,8 +136,7 @@ namespace osu.Game.Rulesets.Osu.Mods // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) // is proportional to the distance between the last and the current hit object // to allow jumps and prevent too sharp turns during streams. - var maxDistance = OsuPlayfield.BASE_SIZE.LengthFast; - var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * distanceToPrev / maxDistance; + var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * distanceToPrev / playfield_diagonal; currentObjectInfo.AngleRad = (float)randomAngleRad + prevObjectInfo.AngleRad; if (currentObjectInfo.AngleRad < 0) @@ -145,6 +150,7 @@ namespace osu.Game.Rulesets.Osu.Mods posRelativeToPrev = getRotatedVector(prevObjectInfo.EndPositionRandomised, posRelativeToPrev); currentObjectInfo.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); + var position = Vector2.Add(prevObjectInfo.EndPositionRandomised, posRelativeToPrev); // Move hit objects back into the playfield if they are outside of it, @@ -192,9 +198,8 @@ namespace osu.Game.Rulesets.Osu.Mods private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev) { var relativeRotationDistance = 0f; - var playfieldMiddle = Vector2.Divide(OsuPlayfield.BASE_SIZE, 2); - if (prevPosChanged.X < playfieldMiddle.X) + if (prevPosChanged.X < playfield_middle.X) { relativeRotationDistance = Math.Max( (border_distance_x - prevPosChanged.X) / border_distance_x, @@ -209,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Mods ); } - if (prevPosChanged.Y < playfieldMiddle.Y) + if (prevPosChanged.Y < playfield_middle.Y) { relativeRotationDistance = Math.Max( (border_distance_y - prevPosChanged.Y) / border_distance_y, @@ -224,11 +229,7 @@ namespace osu.Game.Rulesets.Osu.Mods ); } - return rotateVectorTowardsVector( - posRelativeToPrev, - Vector2.Subtract(playfieldMiddle, prevPosChanged), - relativeRotationDistance / 2 - ); + return rotateVectorTowardsVector(posRelativeToPrev, playfield_middle - prevPosChanged, relativeRotationDistance / 2); } /// From a92ded8a2fc8e9e947d1258777000d5252aa81cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 14:28:07 +0900 Subject: [PATCH 0285/1621] Apply renaming and general code clean-up --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index bbae746f67..61559c06b9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (hitObject) { case HitCircle circle: - getObjectInfo( + applyRandomisation( rateOfChangeMultiplier, prevObjectInfo, distanceToPrev, @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Mods currentObjectInfo.EndPositionOriginal = slider.TailCircle.Position; - getObjectInfo( + applyRandomisation( rateOfChangeMultiplier, prevObjectInfo, distanceToPrev, @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// Returns the final position of the hit object /// /// Final position of the hit object - private void getObjectInfo(float rateOfChangeMultiplier, RandomObjectInfo prevObjectInfo, float distanceToPrev, ref RandomObjectInfo currentObjectInfo) + private void applyRandomisation(float rateOfChangeMultiplier, RandomObjectInfo prevObjectInfo, float distanceToPrev, ref RandomObjectInfo currentObjectInfo) { // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) // is proportional to the distance between the last and the current hit object @@ -155,15 +155,8 @@ namespace osu.Game.Rulesets.Osu.Mods // Move hit objects back into the playfield if they are outside of it, // which would sometimes happen during big jumps otherwise. - if (position.X < 0) - position.X = 0; - else if (position.X > OsuPlayfield.BASE_SIZE.X) - position.X = OsuPlayfield.BASE_SIZE.X; - - if (position.Y < 0) - position.Y = 0; - else if (position.Y > OsuPlayfield.BASE_SIZE.Y) - position.Y = OsuPlayfield.BASE_SIZE.Y; + position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X); + position.Y = MathHelper.Clamp(position.Y, 0, OsuPlayfield.BASE_SIZE.Y); currentObjectInfo.PositionRandomised = position; } From 53b5341bb98d7427b53edccbaed14bdd5250fbe5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 May 2021 14:33:07 +0900 Subject: [PATCH 0286/1621] Simplify application logic --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 52 +++++++--------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 61559c06b9..cc7732372f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -83,36 +83,24 @@ namespace osu.Game.Rulesets.Osu.Mods var distanceToPrev = Vector2.Distance(prevObjectInfo.EndPositionOriginal, currentObjectInfo.PositionOriginal); + if (hitObject is Spinner) + continue; + + applyRandomisation( + rateOfChangeMultiplier, + prevObjectInfo, + distanceToPrev, + ref currentObjectInfo + ); + + hitObject.Position = currentObjectInfo.PositionRandomised; + + // update end position as it may have changed as a result of the position update. + currentObjectInfo.EndPositionRandomised = currentObjectInfo.PositionRandomised; + switch (hitObject) { - case HitCircle circle: - applyRandomisation( - rateOfChangeMultiplier, - prevObjectInfo, - distanceToPrev, - ref currentObjectInfo - ); - - circle.Position = currentObjectInfo.PositionRandomised; - currentObjectInfo.EndPositionRandomised = currentObjectInfo.PositionRandomised; - - break; - case Slider slider: - currentObjectInfo.EndPositionOriginal = slider.EndPosition; - - currentObjectInfo.EndPositionOriginal = slider.TailCircle.Position; - - applyRandomisation( - rateOfChangeMultiplier, - prevObjectInfo, - distanceToPrev, - ref currentObjectInfo - ); - - slider.Position = currentObjectInfo.PositionRandomised; - currentObjectInfo.EndPositionRandomised = slider.TailCircle.Position; - moveSliderIntoPlayfield(ref slider, ref currentObjectInfo); var sliderShift = Vector2.Subtract(slider.Position, currentObjectInfo.PositionOriginal); @@ -239,15 +227,9 @@ namespace osu.Game.Rulesets.Osu.Mods var diff = destAngleRad - initialAngleRad; - while (diff < -Math.PI) - { - diff += 2 * Math.PI; - } + while (diff < -Math.PI) diff += 2 * Math.PI; - while (diff > Math.PI) - { - diff -= 2 * Math.PI; - } + while (diff > Math.PI) diff -= 2 * Math.PI; var finalAngleRad = initialAngleRad + relativeDistance * diff; From 098d8c213178827f6b759ec9841cbb6a8cb8549b Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 24 May 2021 15:13:31 +0200 Subject: [PATCH 0287/1621] Add complete randomisation for first hit object and hit objects after spinners --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 33 ++++++++++++++-------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index cc7732372f..ad2f4585f6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -58,35 +58,32 @@ namespace osu.Game.Rulesets.Osu.Mods var rng = new Random((int)Seed.Value); - var prevObjectInfo = new RandomObjectInfo - { - PositionOriginal = hitObjects[0].Position, - EndPositionOriginal = hitObjects[0].EndPosition, - PositionRandomised = hitObjects[0].Position, - EndPositionRandomised = hitObjects[0].EndPosition - }; + RandomObjectInfo? prevObjectInfo = null; float rateOfChangeMultiplier = 0; for (int i = 0; i < hitObjects.Count; i++) { + var distanceToPrev = 0f; + var hitObject = hitObjects[i]; var currentObjectInfo = new RandomObjectInfo(hitObject); - if (i == 0) - prevObjectInfo = currentObjectInfo; + if (i > 0 && hitObjects[i - 1] is Spinner) + prevObjectInfo = null; + else if (prevObjectInfo != null) + distanceToPrev = Vector2.Distance(((RandomObjectInfo)prevObjectInfo).EndPositionOriginal, currentObjectInfo.PositionOriginal); // rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams if (i % 3 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; - var distanceToPrev = Vector2.Distance(prevObjectInfo.EndPositionOriginal, currentObjectInfo.PositionOriginal); - if (hitObject is Spinner) continue; applyRandomisation( + rng, rateOfChangeMultiplier, prevObjectInfo, distanceToPrev, @@ -119,8 +116,20 @@ namespace osu.Game.Rulesets.Osu.Mods /// Returns the final position of the hit object /// /// Final position of the hit object - private void applyRandomisation(float rateOfChangeMultiplier, RandomObjectInfo prevObjectInfo, float distanceToPrev, ref RandomObjectInfo currentObjectInfo) + private void applyRandomisation(Random rng, float rateOfChangeMultiplier, RandomObjectInfo? prevObjectInfoNullable, float distanceToPrev, ref RandomObjectInfo currentObjectInfo) { + if (prevObjectInfoNullable == null) + { + var playfieldSize = OsuPlayfield.BASE_SIZE; + + currentObjectInfo.AngleRad = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); + currentObjectInfo.PositionRandomised = new Vector2((float)rng.NextDouble() * playfieldSize.X, (float)rng.NextDouble() * playfieldSize.Y); + + return; + } + + var prevObjectInfo = (RandomObjectInfo)prevObjectInfoNullable; + // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) // is proportional to the distance between the last and the current hit object // to allow jumps and prevent too sharp turns during streams. From 2a87b3d74bdf0bbecaea497d6992fb6760b667d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 13:43:55 +0900 Subject: [PATCH 0288/1621] 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 0289/1621] 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 0290/1621] 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 a3c78674a12022506f8cd42d2407e7dfe8ad176a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 18:09:24 +0900 Subject: [PATCH 0291/1621] Add new interface for autoplay mods --- .../Mods/EmptyFreeformModAutoplay.cs | 3 +-- .../Mods/PippidonModAutoplay.cs | 3 +-- .../Mods/EmptyScrollingModAutoplay.cs | 3 +-- .../Mods/PippidonModAutoplay.cs | 3 +-- osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs | 3 +-- osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs | 3 +-- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 3 +-- osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs | 3 +-- osu.Game/Rulesets/Mods/ICreateReplay.cs | 14 ++++++++++++++ osu.Game/Rulesets/Mods/ModAutoplay.cs | 13 +------------ 10 files changed, 23 insertions(+), 28 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ICreateReplay.cs diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.cs index d5c1e9bd15..f705009d18 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Rulesets.EmptyFreeform.Objects; using osu.Game.Rulesets.EmptyFreeform.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -11,7 +10,7 @@ using osu.Game.Users; namespace osu.Game.Rulesets.EmptyFreeform.Mods { - public class EmptyFreeformModAutoplay : ModAutoplay + public class EmptyFreeformModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs index 8ea334c99c..4565c97d1a 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs @@ -4,14 +4,13 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Pippidon.Objects; using osu.Game.Rulesets.Pippidon.Replays; using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Pippidon.Mods { - public class PippidonModAutoplay : ModAutoplay + public class PippidonModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.cs index 6dad1ff43b..431994e098 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.cs @@ -3,7 +3,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.EmptyScrolling.Objects; using osu.Game.Rulesets.EmptyScrolling.Replays; using osu.Game.Scoring; using osu.Game.Users; @@ -11,7 +10,7 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.EmptyScrolling.Mods { - public class EmptyScrollingModAutoplay : ModAutoplay + public class EmptyScrollingModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs index 8ea334c99c..4565c97d1a 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs @@ -4,14 +4,13 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Pippidon.Objects; using osu.Game.Rulesets.Pippidon.Replays; using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Pippidon.Mods { - public class PippidonModAutoplay : ModAutoplay + public class PippidonModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs index e1eceea606..f1b51e51d0 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -11,7 +10,7 @@ using osu.Game.Users; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModAutoplay : ModAutoplay + public class CatchModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs index 105d88129c..6ae854e7f3 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -12,7 +11,7 @@ using osu.Game.Users; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModAutoplay : ModAutoplay + public class ManiaModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index 3b1f271d41..652da7123e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -6,14 +6,13 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAutoplay : ModAutoplay + public class OsuModAutoplay : ModAutoplay { public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs index 64e59b64d0..31d9abf8b2 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs @@ -4,14 +4,13 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModAutoplay : ModAutoplay + public class TaikoModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/osu.Game/Rulesets/Mods/ICreateReplay.cs b/osu.Game/Rulesets/Mods/ICreateReplay.cs new file mode 100644 index 0000000000..098bd8799a --- /dev/null +++ b/osu.Game/Rulesets/Mods/ICreateReplay.cs @@ -0,0 +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.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Scoring; + +namespace osu.Game.Rulesets.Mods +{ + public interface ICreateReplay + { + public Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods); + } +} diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index d6e1d46b06..b84b5671e1 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -7,22 +7,11 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Replays; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI; using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModAutoplay : ModAutoplay, IApplicableToDrawableRuleset - where T : HitObject - { - public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap, drawableRuleset.Mods)); - } - } - - public abstract class ModAutoplay : Mod, IApplicableFailOverride + public abstract class ModAutoplay : Mod, IApplicableFailOverride, ICreateReplay { public override string Name => "Autoplay"; public override string Acronym => "AT"; From c2b938a29f5fef339a3adeb9ab62fa647a06dc12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 18:09:37 +0900 Subject: [PATCH 0292/1621] Remove autoplay consideration from `Player` --- osu.Game/Screens/Play/Player.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 39f9e2d388..b818dbea08 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -161,9 +161,7 @@ namespace osu.Game.Screens.Play if (!LoadedBeatmapSuccessfully) return; - // replays should never be recorded or played back when autoplay is enabled - if (!Mods.Value.Any(m => m is ModAutoplay)) - PrepareReplay(); + PrepareReplay(); gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true); } From 7f9318d97658d1dcbce49162c1fa3d7d9bdde4db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 18:36:47 +0900 Subject: [PATCH 0293/1621] Expose `GameplayBeatmap` to derived `Player` classes --- osu.Game/Screens/Play/Player.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b818dbea08..8e9c2fadcd 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -145,7 +145,7 @@ namespace osu.Game.Screens.Play Configuration = configuration ?? new PlayerConfiguration(); } - private GameplayBeatmap gameplayBeatmap; + protected GameplayBeatmap GameplayBeatmap { get; private set; } private ScreenSuspensionHandler screenSuspension; @@ -221,10 +221,10 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime); - AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap)); + AddInternal(GameplayBeatmap = new GameplayBeatmap(playableBeatmap)); AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); - dependencies.CacheAs(gameplayBeatmap); + dependencies.CacheAs(GameplayBeatmap); var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); @@ -282,7 +282,7 @@ namespace osu.Game.Screens.Play { HealthProcessor.ApplyResult(r); ScoreProcessor.ApplyResult(r); - gameplayBeatmap.ApplyResult(r); + GameplayBeatmap.ApplyResult(r); }; DrawableRuleset.RevertResult += r => @@ -946,7 +946,7 @@ namespace osu.Game.Screens.Play using (var stream = new MemoryStream()) { - new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream); + new LegacyScoreEncoder(score, GameplayBeatmap.PlayableBeatmap).Encode(stream); replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); } From 7c89dbcd3502e68639476f9d99ab92e63205c870 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 18:37:04 +0900 Subject: [PATCH 0294/1621] Externalise autoplay generation --- osu.Game/Screens/Play/ReplayPlayer.cs | 21 ++++++++++++++++-- osu.Game/Screens/Select/PlaySongSelect.cs | 26 +++++++++++++++-------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index e23cc22929..07c3d197da 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -1,9 +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 System.Collections.Generic; using System.Threading.Tasks; +using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -11,15 +15,28 @@ namespace osu.Game.Screens.Play { public class ReplayPlayer : Player, IKeyBindingHandler { - protected readonly Score Score; + protected Score Score { get; private set; } + + private readonly Func, Score> createScore; // Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108) protected override bool CheckModsAllowFailure() => false; public ReplayPlayer(Score score, PlayerConfiguration configuration = null) + : this((_, __) => score, configuration) + { + } + + public ReplayPlayer(Func, Score> createScore, PlayerConfiguration configuration = null) : base(configuration) { - Score = score; + this.createScore = createScore; + } + + [BackgroundDependencyLoader] + private void load() + { + Score = createScore(GameplayBeatmap, Mods.Value); } protected override void PrepareReplay() diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index dfb4b59060..357222c109 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Select public class PlaySongSelect : SongSelect { private bool removeAutoModOnResume; - private OsuScreen player; + private OsuScreen playerLoader; [Resolved(CanBeNull = true)] private NotificationOverlay notifications { get; set; } @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select { base.OnResuming(last); - player = null; + playerLoader = null; if (removeAutoModOnResume) { @@ -79,14 +79,14 @@ namespace osu.Game.Screens.Select protected override bool OnStart() { - if (player != null) return false; + if (playerLoader != null) return false; // Ctrl+Enter should start map with autoplay enabled. if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true) { - var autoplayMod = getAutoplayMod(); + var autoInstance = getAutoplayMod(); - if (autoplayMod == null) + if (autoInstance == null) { notifications?.Post(new SimpleNotification { @@ -97,18 +97,26 @@ namespace osu.Game.Screens.Select var mods = Mods.Value; - if (mods.All(m => m.GetType() != autoplayMod.GetType())) + if (mods.All(m => m.GetType() != autoInstance.GetType())) { - Mods.Value = mods.Append(autoplayMod).ToArray(); + Mods.Value = mods.Append(autoInstance).ToArray(); removeAutoModOnResume = true; } } SampleConfirm?.Play(); - this.Push(player = new PlayerLoader(() => new SoloPlayer())); - + this.Push(playerLoader = new PlayerLoader(createPlayer)); return true; + + Player createPlayer() + { + var autoplayMod = Mods.Value.OfType().FirstOrDefault(); + if (autoplayMod != null) + return new ReplayPlayer((beatmap, mods) => autoplayMod.CreateReplayScore(beatmap, mods)); + + return new SoloPlayer(); + } } } } From bdbd64c88d6a9cace3862fcdd45ccb6109d38cda Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Tue, 25 May 2021 21:32:18 +0200 Subject: [PATCH 0295/1621] Fix sliders being partly outside of the playfield in some cases --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 68 ++++++++++++++++------ 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index ad2f4585f6..98488fef8c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -98,13 +98,9 @@ namespace osu.Game.Rulesets.Osu.Mods switch (hitObject) { case Slider slider: + shiftNestedObjects(slider, Vector2.Subtract(slider.Position, currentObjectInfo.PositionOriginal)); moveSliderIntoPlayfield(ref slider, ref currentObjectInfo); - var sliderShift = Vector2.Subtract(slider.Position, currentObjectInfo.PositionOriginal); - - foreach (var tick in slider.NestedHitObjects.OfType()) - tick.Position = Vector2.Add(tick.Position, sliderShift); - break; } @@ -158,27 +154,61 @@ namespace osu.Game.Rulesets.Osu.Mods currentObjectInfo.PositionRandomised = position; } + /// + /// Moves the and all necessary nested s into the if they aren't already. + /// private void moveSliderIntoPlayfield(ref Slider slider, ref RandomObjectInfo currentObjectInfo) { - foreach (var controlPoint in slider.Path.ControlPoints) + var oldPos = new Vector2(slider.Position.X, slider.Position.Y); + + // Min. distances from the slider's position to the playfield border + var minMargin = new MarginPadding(0); + + foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderEndCircle)) { - // Position of controlPoint relative to slider.Position - var pos = controlPoint.Position.Value; + if (!(hitObject is OsuHitObject osuHitObject)) + continue; - var playfieldSize = OsuPlayfield.BASE_SIZE; + var relativePos = Vector2.Subtract(osuHitObject.Position, slider.Position); - if (pos.X + slider.Position.X < 0) - slider.Position = new Vector2(-pos.X, slider.Position.Y); - else if (pos.X + slider.Position.X > playfieldSize.X) - slider.Position = new Vector2(playfieldSize.X - pos.X, slider.Position.Y); - - if (pos.Y + slider.Position.Y < 0) - slider.Position = new Vector2(slider.Position.X, -pos.Y); - else if (pos.Y + slider.Position.Y > playfieldSize.Y) - slider.Position = new Vector2(slider.Position.X, playfieldSize.Y - pos.Y); + minMargin.Left = Math.Max(minMargin.Left, -relativePos.X); + minMargin.Right = Math.Max(minMargin.Right, relativePos.X); + minMargin.Top = Math.Max(minMargin.Top, -relativePos.Y); + minMargin.Bottom = Math.Max(minMargin.Bottom, relativePos.Y); } - currentObjectInfo.EndPositionRandomised = slider.TailCircle.Position; + if (slider.Position.X < minMargin.Left) + slider.Position = new Vector2(minMargin.Left, slider.Position.Y); + else if (slider.Position.X + minMargin.Right > OsuPlayfield.BASE_SIZE.X) + slider.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - minMargin.Right, slider.Position.Y); + + if (slider.Position.Y < minMargin.Top) + slider.Position = new Vector2(slider.Position.X, minMargin.Top); + else if (slider.Position.Y + minMargin.Bottom > OsuPlayfield.BASE_SIZE.Y) + slider.Position = new Vector2(slider.Position.X, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); + + currentObjectInfo.PositionRandomised = slider.Position; + currentObjectInfo.EndPositionRandomised = slider.EndPosition; + + var shift = Vector2.Subtract(slider.Position, oldPos); + + shiftNestedObjects(slider, shift); + } + + /// + /// Shifts all nested s and s by the specified shift. + /// + /// whose nested s and s should be shifted + /// The the 's nested s and s should be shifted by + private void shiftNestedObjects(Slider slider, Vector2 shift) + { + foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderRepeat)) + { + if (!(hitObject is OsuHitObject osuHitObject)) + continue; + + osuHitObject.Position = Vector2.Add(osuHitObject.Position, shift); + } } /// From c85d5513bedb30f407594555b6bdaba3d10a2864 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Tue, 25 May 2021 21:42:26 +0200 Subject: [PATCH 0296/1621] Remove redundant parameter and unused setters --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 98488fef8c..0f6b6d1afa 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Osu.Mods var oldPos = new Vector2(slider.Position.X, slider.Position.Y); // Min. distances from the slider's position to the playfield border - var minMargin = new MarginPadding(0); + var minMargin = new MarginPadding(); foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderEndCircle)) { @@ -282,10 +282,10 @@ namespace osu.Game.Rulesets.Osu.Mods { internal float AngleRad { get; set; } - internal Vector2 PositionOriginal { get; set; } + internal Vector2 PositionOriginal { get; } internal Vector2 PositionRandomised { get; set; } - internal Vector2 EndPositionOriginal { get; set; } + internal Vector2 EndPositionOriginal { get; } internal Vector2 EndPositionRandomised { get; set; } public RandomObjectInfo(OsuHitObject hitObject) From 88bdd8a7b767552c16f676e64f25585aecb41ff6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 16:01:20 +0900 Subject: [PATCH 0297/1621] 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 c5ff05209602584c388c8263b65395a0b8f1d22f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 16:31:25 +0900 Subject: [PATCH 0298/1621] Change `internal` to `public` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 0f6b6d1afa..b81e9fe3c5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -280,13 +280,13 @@ namespace osu.Game.Rulesets.Osu.Mods private struct RandomObjectInfo { - internal float AngleRad { get; set; } + public float AngleRad { get; set; } - internal Vector2 PositionOriginal { get; } - internal Vector2 PositionRandomised { get; set; } + public Vector2 PositionOriginal { get; } + public Vector2 PositionRandomised { get; set; } - internal Vector2 EndPositionOriginal { get; } - internal Vector2 EndPositionRandomised { get; set; } + public Vector2 EndPositionOriginal { get; } + public Vector2 EndPositionRandomised { get; set; } public RandomObjectInfo(OsuHitObject hitObject) { From 6181b1ac92302f5c8bae6f93ff2091da5cf81bde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 16:36:14 +0900 Subject: [PATCH 0299/1621] Simplify previous object handling by using a class instead of struct --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 31 +++++++++++----------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index b81e9fe3c5..d1d1d24f1b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Mods var rng = new Random((int)Seed.Value); - RandomObjectInfo? prevObjectInfo = null; + RandomObjectInfo prevObjectInfo = null; float rateOfChangeMultiplier = 0; @@ -70,24 +70,25 @@ namespace osu.Game.Rulesets.Osu.Mods var currentObjectInfo = new RandomObjectInfo(hitObject); - if (i > 0 && hitObjects[i - 1] is Spinner) - prevObjectInfo = null; - else if (prevObjectInfo != null) - distanceToPrev = Vector2.Distance(((RandomObjectInfo)prevObjectInfo).EndPositionOriginal, currentObjectInfo.PositionOriginal); + if (prevObjectInfo != null) + distanceToPrev = Vector2.Distance(prevObjectInfo.EndPositionOriginal, currentObjectInfo.PositionOriginal); // rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams if (i % 3 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; if (hitObject is Spinner) + { + prevObjectInfo = null; continue; + } applyRandomisation( rng, rateOfChangeMultiplier, prevObjectInfo, distanceToPrev, - ref currentObjectInfo + currentObjectInfo ); hitObject.Position = currentObjectInfo.PositionRandomised; @@ -99,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Mods { case Slider slider: shiftNestedObjects(slider, Vector2.Subtract(slider.Position, currentObjectInfo.PositionOriginal)); - moveSliderIntoPlayfield(ref slider, ref currentObjectInfo); + moveSliderIntoPlayfield(slider, currentObjectInfo); break; } @@ -112,9 +113,9 @@ namespace osu.Game.Rulesets.Osu.Mods /// Returns the final position of the hit object /// /// Final position of the hit object - private void applyRandomisation(Random rng, float rateOfChangeMultiplier, RandomObjectInfo? prevObjectInfoNullable, float distanceToPrev, ref RandomObjectInfo currentObjectInfo) + private void applyRandomisation(float rateOfChangeMultiplier, RandomObjectInfo prevObject, float distanceToPrev, RandomObjectInfo currentObjectInfo) { - if (prevObjectInfoNullable == null) + if (prevObject == null) { var playfieldSize = OsuPlayfield.BASE_SIZE; @@ -124,14 +125,12 @@ namespace osu.Game.Rulesets.Osu.Mods return; } - var prevObjectInfo = (RandomObjectInfo)prevObjectInfoNullable; - // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) // is proportional to the distance between the last and the current hit object // to allow jumps and prevent too sharp turns during streams. var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * distanceToPrev / playfield_diagonal; - currentObjectInfo.AngleRad = (float)randomAngleRad + prevObjectInfo.AngleRad; + currentObjectInfo.AngleRad = (float)randomAngleRad + prevObject.AngleRad; if (currentObjectInfo.AngleRad < 0) currentObjectInfo.AngleRad += 2 * (float)Math.PI; @@ -140,11 +139,11 @@ namespace osu.Game.Rulesets.Osu.Mods distanceToPrev * (float)Math.Sin(currentObjectInfo.AngleRad) ); - posRelativeToPrev = getRotatedVector(prevObjectInfo.EndPositionRandomised, posRelativeToPrev); + posRelativeToPrev = getRotatedVector(prevObject.EndPositionRandomised, posRelativeToPrev); currentObjectInfo.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); - var position = Vector2.Add(prevObjectInfo.EndPositionRandomised, posRelativeToPrev); + var position = Vector2.Add(prevObject.EndPositionRandomised, posRelativeToPrev); // Move hit objects back into the playfield if they are outside of it, // which would sometimes happen during big jumps otherwise. @@ -157,7 +156,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Moves the and all necessary nested s into the if they aren't already. /// - private void moveSliderIntoPlayfield(ref Slider slider, ref RandomObjectInfo currentObjectInfo) + private void moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) { var oldPos = new Vector2(slider.Position.X, slider.Position.Y); @@ -278,7 +277,7 @@ namespace osu.Game.Rulesets.Osu.Mods ); } - private struct RandomObjectInfo + private class RandomObjectInfo { public float AngleRad { get; set; } From 6ca9b37c28279e05d599903047c2be4484a28ce6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 16:37:30 +0900 Subject: [PATCH 0300/1621] Make random generator a field to avoid passing around internally --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index d1d1d24f1b..c53c262ffb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Mods private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; + private Random rng; + [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(OsuModRandomSettingsControl))] public Bindable Seed { get; } = new Bindable { @@ -56,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Mods Seed.Value ??= RNG.Next(); - var rng = new Random((int)Seed.Value); + rng = new Random((int)Seed.Value); RandomObjectInfo prevObjectInfo = null; @@ -83,9 +85,7 @@ namespace osu.Game.Rulesets.Osu.Mods continue; } - applyRandomisation( - rng, - rateOfChangeMultiplier, + applyRandomisation(rateOfChangeMultiplier, prevObjectInfo, distanceToPrev, currentObjectInfo From ad3e4287cd24da058cff136293b3b7913b68730c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 16:44:05 +0900 Subject: [PATCH 0301/1621] Move `distanceToPrev` inside randomisation function --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 39 +++++++++------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index c53c262ffb..1c59569517 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -66,15 +66,10 @@ namespace osu.Game.Rulesets.Osu.Mods for (int i = 0; i < hitObjects.Count; i++) { - var distanceToPrev = 0f; - var hitObject = hitObjects[i]; var currentObjectInfo = new RandomObjectInfo(hitObject); - if (prevObjectInfo != null) - distanceToPrev = Vector2.Distance(prevObjectInfo.EndPositionOriginal, currentObjectInfo.PositionOriginal); - // rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams if (i % 3 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; @@ -85,11 +80,7 @@ namespace osu.Game.Rulesets.Osu.Mods continue; } - applyRandomisation(rateOfChangeMultiplier, - prevObjectInfo, - distanceToPrev, - currentObjectInfo - ); + applyRandomisation(rateOfChangeMultiplier, prevObjectInfo, currentObjectInfo); hitObject.Position = currentObjectInfo.PositionRandomised; @@ -113,44 +104,46 @@ namespace osu.Game.Rulesets.Osu.Mods /// Returns the final position of the hit object /// /// Final position of the hit object - private void applyRandomisation(float rateOfChangeMultiplier, RandomObjectInfo prevObject, float distanceToPrev, RandomObjectInfo currentObjectInfo) + private void applyRandomisation(float rateOfChangeMultiplier, RandomObjectInfo previous, RandomObjectInfo current) { - if (prevObject == null) + if (previous == null) { var playfieldSize = OsuPlayfield.BASE_SIZE; - currentObjectInfo.AngleRad = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); - currentObjectInfo.PositionRandomised = new Vector2((float)rng.NextDouble() * playfieldSize.X, (float)rng.NextDouble() * playfieldSize.Y); + current.AngleRad = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); + current.PositionRandomised = new Vector2((float)rng.NextDouble() * playfieldSize.X, (float)rng.NextDouble() * playfieldSize.Y); return; } + float distanceToPrev = Vector2.Distance(previous.EndPositionOriginal, current.PositionOriginal); + // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) // is proportional to the distance between the last and the current hit object // to allow jumps and prevent too sharp turns during streams. var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * distanceToPrev / playfield_diagonal; - currentObjectInfo.AngleRad = (float)randomAngleRad + prevObject.AngleRad; - if (currentObjectInfo.AngleRad < 0) - currentObjectInfo.AngleRad += 2 * (float)Math.PI; + current.AngleRad = (float)randomAngleRad + previous.AngleRad; + if (current.AngleRad < 0) + current.AngleRad += 2 * (float)Math.PI; var posRelativeToPrev = new Vector2( - distanceToPrev * (float)Math.Cos(currentObjectInfo.AngleRad), - distanceToPrev * (float)Math.Sin(currentObjectInfo.AngleRad) + distanceToPrev * (float)Math.Cos(current.AngleRad), + distanceToPrev * (float)Math.Sin(current.AngleRad) ); - posRelativeToPrev = getRotatedVector(prevObject.EndPositionRandomised, posRelativeToPrev); + posRelativeToPrev = getRotatedVector(previous.EndPositionRandomised, posRelativeToPrev); - currentObjectInfo.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); + current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); - var position = Vector2.Add(prevObject.EndPositionRandomised, posRelativeToPrev); + var position = Vector2.Add(previous.EndPositionRandomised, posRelativeToPrev); // Move hit objects back into the playfield if they are outside of it, // which would sometimes happen during big jumps otherwise. position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X); position.Y = MathHelper.Clamp(position.Y, 0, OsuPlayfield.BASE_SIZE.Y); - currentObjectInfo.PositionRandomised = position; + current.PositionRandomised = position; } /// From d6c4be207b05530b39fc520cb03a28f7c082680b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 16:44:44 +0900 Subject: [PATCH 0302/1621] Simplify naming --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 1c59569517..a9aeba99fa 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Mods rng = new Random((int)Seed.Value); - RandomObjectInfo prevObjectInfo = null; + RandomObjectInfo previous = null; float rateOfChangeMultiplier = 0; @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var hitObject = hitObjects[i]; - var currentObjectInfo = new RandomObjectInfo(hitObject); + var current = new RandomObjectInfo(hitObject); // rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams if (i % 3 == 0) @@ -76,27 +76,27 @@ namespace osu.Game.Rulesets.Osu.Mods if (hitObject is Spinner) { - prevObjectInfo = null; + previous = null; continue; } - applyRandomisation(rateOfChangeMultiplier, prevObjectInfo, currentObjectInfo); + applyRandomisation(rateOfChangeMultiplier, previous, current); - hitObject.Position = currentObjectInfo.PositionRandomised; + hitObject.Position = current.PositionRandomised; // update end position as it may have changed as a result of the position update. - currentObjectInfo.EndPositionRandomised = currentObjectInfo.PositionRandomised; + current.EndPositionRandomised = current.PositionRandomised; switch (hitObject) { case Slider slider: - shiftNestedObjects(slider, Vector2.Subtract(slider.Position, currentObjectInfo.PositionOriginal)); - moveSliderIntoPlayfield(slider, currentObjectInfo); + shiftNestedObjects(slider, Vector2.Subtract(slider.Position, current.PositionOriginal)); + moveSliderIntoPlayfield(slider, current); break; } - prevObjectInfo = currentObjectInfo; + previous = current; } } From a08a4aa9111448432fb708fdc17a8014f66a1c59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 May 2021 16:48:16 +0900 Subject: [PATCH 0303/1621] Move second call to `shiftNestedObjects` to a more understandable location --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index a9aeba99fa..58ace92905 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -91,8 +91,13 @@ namespace osu.Game.Rulesets.Osu.Mods { case Slider slider: shiftNestedObjects(slider, Vector2.Subtract(slider.Position, current.PositionOriginal)); + + var oldPos = new Vector2(slider.Position.X, slider.Position.Y); + moveSliderIntoPlayfield(slider, current); + shiftNestedObjects(slider, Vector2.Subtract(slider.Position, oldPos)); + break; } @@ -151,8 +156,6 @@ namespace osu.Game.Rulesets.Osu.Mods /// private void moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) { - var oldPos = new Vector2(slider.Position.X, slider.Position.Y); - // Min. distances from the slider's position to the playfield border var minMargin = new MarginPadding(); @@ -181,10 +184,6 @@ namespace osu.Game.Rulesets.Osu.Mods currentObjectInfo.PositionRandomised = slider.Position; currentObjectInfo.EndPositionRandomised = slider.EndPosition; - - var shift = Vector2.Subtract(slider.Position, oldPos); - - shiftNestedObjects(slider, shift); } /// From d5de5ae6406f85fee9217c8c1649d93e0547e5dc Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Wed, 26 May 2021 20:50:31 +0200 Subject: [PATCH 0304/1621] Add comments explaining the usage of `shiftNestedObjects()` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 58ace92905..7b28675511 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -90,12 +90,15 @@ namespace osu.Game.Rulesets.Osu.Mods switch (hitObject) { case Slider slider: + // Shift nested objects the same distance as the slider got shifted in the randomisation process + // so that moveSliderIntoPlayfield() can determine their relative distances to slider.Position and thus minMargin shiftNestedObjects(slider, Vector2.Subtract(slider.Position, current.PositionOriginal)); var oldPos = new Vector2(slider.Position.X, slider.Position.Y); moveSliderIntoPlayfield(slider, current); + // Shift them again to move them to their final position after the slider got moved into the playfield shiftNestedObjects(slider, Vector2.Subtract(slider.Position, oldPos)); break; From d47370bac93ad869c7505c58bfdf003b133c2745 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 27 May 2021 00:59:29 +0200 Subject: [PATCH 0305/1621] 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 0306/1621] 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 0307/1621] 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 0308/1621] 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 4fd89faaa459d26ed3467675ad391d10c08dc1da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 11:48:48 +0900 Subject: [PATCH 0309/1621] Fix default skin not having resources or providing samples --- osu.Game/Skinning/DefaultSkin.cs | 15 ++++++++++++++- osu.Game/Skinning/SkinManager.cs | 4 +++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index ba31816a07..a17a052b97 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -22,6 +22,8 @@ namespace osu.Game.Skinning { public class DefaultSkin : Skin { + private readonly IStorageResourceProvider resources; + public DefaultSkin(IStorageResourceProvider resources) : this(SkinInfo.Default, resources) { @@ -31,12 +33,23 @@ namespace osu.Game.Skinning public DefaultSkin(SkinInfo skin, IStorageResourceProvider resources) : base(skin, resources) { + this.resources = resources; Configuration = new DefaultSkinConfiguration(); } public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; - public override ISample GetSample(ISampleInfo sampleInfo) => null; + public override ISample GetSample(ISampleInfo sampleInfo) + { + foreach (var lookup in sampleInfo.LookupNames) + { + var sample = resources.AudioManager.Samples.Get(lookup); + if (sample != null) + return sample; + } + + return null; + } public override Drawable GetDrawableComponent(ISkinComponent component) { diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 5793edda30..66c776d32d 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -39,7 +39,7 @@ namespace osu.Game.Skinning private readonly IResourceStore legacyDefaultResources; - public readonly Bindable CurrentSkin = new Bindable(new DefaultSkin(null)); + public readonly Bindable CurrentSkin = new Bindable(); public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; public override IEnumerable HandledExtensions => new[] { ".osk" }; @@ -56,6 +56,8 @@ namespace osu.Game.Skinning this.legacyDefaultResources = legacyDefaultResources; + CurrentSkin.Value = new DefaultSkin(this); + CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue); CurrentSkin.ValueChanged += skin => { From 70a844ac10409380692e05343f21b2afee5f9229 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 14:50:42 +0900 Subject: [PATCH 0310/1621] Remove `allowFallback` parameters completely --- .../UI/CatchComboDisplay.cs | 4 +-- osu.Game.Rulesets.Catch/UI/Catcher.cs | 4 +-- .../UI/Components/HitObjectArea.cs | 4 +-- .../Objects/Drawables/SkinnableLighting.cs | 4 +-- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 2 +- .../Gameplay/TestSceneSkinnableDrawable.cs | 31 +++++++++---------- osu.Game/Skinning/PoolableSkinnableSample.cs | 13 ++------ osu.Game/Skinning/SkinManager.cs | 4 +-- osu.Game/Skinning/SkinReloadableDrawable.cs | 21 ++----------- osu.Game/Skinning/SkinnableDrawable.cs | 13 +++----- osu.Game/Skinning/SkinnableSprite.cs | 5 ++- osu.Game/Skinning/SkinnableSpriteText.cs | 8 ++--- osu.Game/Skinning/SkinnableTargetContainer.cs | 4 +-- .../Drawables/DrawableStoryboardSample.cs | 4 +-- 14 files changed, 45 insertions(+), 76 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs index 75feb21298..ad344ff2dd 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs @@ -25,9 +25,9 @@ namespace osu.Game.Rulesets.Catch.UI { } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected override void SkinChanged(ISkinSource skin) { - base.SkinChanged(skin, allowFallback); + base.SkinChanged(skin); ComboCounter?.UpdateCombo(currentCombo); } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 0d6a577d1e..b8c4d8f036 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -399,9 +399,9 @@ namespace osu.Game.Rulesets.Catch.UI private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing; - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected override void SkinChanged(ISkinSource skin) { - base.SkinChanged(skin, allowFallback); + base.SkinChanged(skin); hyperDashColour = skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? diff --git a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs index 8f7880dafa..b75b586ecf 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs @@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components Direction.BindValueChanged(onDirectionChanged, true); } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected override void SkinChanged(ISkinSource skin) { - base.SkinChanged(skin, allowFallback); + base.SkinChanged(skin); UpdateHitPosition(); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs index 02dc770285..c72080c9e5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs @@ -18,9 +18,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected override void SkinChanged(ISkinSource skin) { - base.SkinChanged(skin, allowFallback); + base.SkinChanged(skin); updateColour(); } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index eea45c6c80..0e7d7cdcf3 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Size = new Vector2(size); } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected override void SkinChanged(ISkinSource skin) { cursorExpand = skin.GetConfig(OsuSkinConfiguration.CursorExpand)?.Value ?? true; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 7a6e2f54c2..38da7f7104 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Gameplay Spacing = new Vector2(10), Children = new[] { - new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true), - new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true), - new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling) + new ExposedSkinnableDrawable("default", _ => new DefaultBox()), + new ExposedSkinnableDrawable("available", _ => new DefaultBox()), + new ExposedSkinnableDrawable("available", _ => new DefaultBox(), ConfineMode.NoScaling) } }, }; @@ -73,9 +73,9 @@ namespace osu.Game.Tests.Visual.Gameplay Spacing = new Vector2(10), Children = new[] { - new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true), - new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit), - new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling) + new ExposedSkinnableDrawable("default", _ => new DefaultBox()), + new ExposedSkinnableDrawable("available", _ => new DefaultBox(), ConfineMode.ScaleToFit), + new ExposedSkinnableDrawable("available", _ => new DefaultBox(), ConfineMode.NoScaling) } }, }; @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.Gameplay Child = new SkinProvidingContainer(secondarySource) { RelativeSizeAxes = Axes.Both, - Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true) + Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation")) } }; }); @@ -129,7 +129,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true))); + AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation")))); AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox); AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1); } @@ -152,7 +152,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true))); + AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation")))); AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox); AddStep("disable", () => target.Disable()); AddAssert("consumer using base source", () => consumer.Drawable is BaseSourceBox); @@ -180,9 +180,8 @@ namespace osu.Game.Tests.Visual.Gameplay { public new Drawable Drawable => base.Drawable; - public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, - ConfineMode confineMode = ConfineMode.ScaleToFit) - : base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode) + public ExposedSkinnableDrawable(string name, Func defaultImplementation, ConfineMode confineMode = ConfineMode.ScaleToFit) + : base(new TestSkinComponent(name), defaultImplementation, confineMode) { } } @@ -250,14 +249,14 @@ namespace osu.Game.Tests.Visual.Gameplay public new Drawable Drawable => base.Drawable; public int SkinChangedCount { get; private set; } - public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null) - : base(new TestSkinComponent(name), defaultImplementation, allowFallback) + public SkinConsumer(string name, Func defaultImplementation) + : base(new TestSkinComponent(name), defaultImplementation) { } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected override void SkinChanged(ISkinSource skin) { - base.SkinChanged(skin, allowFallback); + base.SkinChanged(skin); SkinChangedCount++; } } diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index b04158a58f..33e8c137f4 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -70,9 +70,9 @@ namespace osu.Game.Skinning updateSample(); } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected override void SkinChanged(ISkinSource skin) { - base.SkinChanged(skin, allowFallback); + base.SkinChanged(skin); updateSample(); } @@ -88,15 +88,6 @@ namespace osu.Game.Skinning var sample = CurrentSkin.GetSample(sampleInfo); - if (sample == null && AllowDefaultFallback) - { - foreach (var lookup in sampleInfo.LookupNames) - { - if ((sample = sampleStore.Get(lookup)) != null) - break; - } - } - if (sample == null) return; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 66c776d32d..503c99d023 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -56,9 +56,9 @@ namespace osu.Game.Skinning this.legacyDefaultResources = legacyDefaultResources; - CurrentSkin.Value = new DefaultSkin(this); - CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue); + + CurrentSkin.Value = new DefaultSkin(this); CurrentSkin.ValueChanged += skin => { if (skin.NewValue.SkinInfo != CurrentSkinInfo.Value) diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs index 50b4143375..dec546b82d 100644 --- a/osu.Game/Skinning/SkinReloadableDrawable.cs +++ b/osu.Game/Skinning/SkinReloadableDrawable.cs @@ -22,22 +22,6 @@ namespace osu.Game.Skinning /// protected ISkinSource CurrentSkin { get; private set; } - private readonly Func allowFallback; - - /// - /// Whether fallback to default skin should be allowed if the custom skin is missing this resource. - /// - protected bool AllowDefaultFallback => allowFallback == null || allowFallback.Invoke(CurrentSkin); - - /// - /// Create a new - /// - /// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present. - protected SkinReloadableDrawable(Func allowFallback = null) - { - this.allowFallback = allowFallback; - } - [BackgroundDependencyLoader] private void load(ISkinSource source) { @@ -58,7 +42,7 @@ namespace osu.Game.Skinning private void skinChanged() { - SkinChanged(CurrentSkin, AllowDefaultFallback); + SkinChanged(CurrentSkin); OnSkinChanged?.Invoke(); } @@ -66,8 +50,7 @@ namespace osu.Game.Skinning /// Called when a change is made to the skin. /// /// The new skin. - /// Whether fallback to default skin should be allowed if the custom skin is missing this resource. - protected virtual void SkinChanged(ISkinSource skin, bool allowFallback) + protected virtual void SkinChanged(ISkinSource skin) { } diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index fc2730ca44..72f64e2e12 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -40,17 +40,14 @@ namespace osu.Game.Skinning /// /// The namespace-complete resource name for this skinnable element. /// A function to create the default skin implementation of this element. - /// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present. /// How (if at all) the should be resize to fit within our own bounds. - public SkinnableDrawable(ISkinComponent component, Func defaultImplementation = null, Func allowFallback = null, - ConfineMode confineMode = ConfineMode.NoScaling) - : this(component, allowFallback, confineMode) + public SkinnableDrawable(ISkinComponent component, Func defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling) + : this(component, confineMode) { createDefault = defaultImplementation; } - protected SkinnableDrawable(ISkinComponent component, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) - : base(allowFallback) + protected SkinnableDrawable(ISkinComponent component, ConfineMode confineMode = ConfineMode.NoScaling) { this.component = component; this.confineMode = confineMode; @@ -76,13 +73,13 @@ namespace osu.Game.Skinning /// protected virtual bool ApplySizeRestrictionsToDefault => false; - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected override void SkinChanged(ISkinSource skin) { Drawable = skin.GetDrawableComponent(component); isDefault = false; - if (Drawable == null && allowFallback) + if (Drawable == null) { Drawable = CreateDefault(component); isDefault = true; diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 1340d1474c..56e576d081 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.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.Sprites; @@ -19,8 +18,8 @@ namespace osu.Game.Skinning [Resolved] private TextureStore textures { get; set; } - public SkinnableSprite(string textureName, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) - : base(new SpriteComponent(textureName), allowFallback, confineMode) + public SkinnableSprite(string textureName, ConfineMode confineMode = ConfineMode.NoScaling) + : base(new SpriteComponent(textureName), confineMode) { } diff --git a/osu.Game/Skinning/SkinnableSpriteText.cs b/osu.Game/Skinning/SkinnableSpriteText.cs index 06461127b1..2bde3c4180 100644 --- a/osu.Game/Skinning/SkinnableSpriteText.cs +++ b/osu.Game/Skinning/SkinnableSpriteText.cs @@ -9,14 +9,14 @@ namespace osu.Game.Skinning { public class SkinnableSpriteText : SkinnableDrawable, IHasText { - public SkinnableSpriteText(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) - : base(component, defaultImplementation, allowFallback, confineMode) + public SkinnableSpriteText(ISkinComponent component, Func defaultImplementation, ConfineMode confineMode = ConfineMode.NoScaling) + : base(component, defaultImplementation, confineMode) { } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected override void SkinChanged(ISkinSource skin) { - base.SkinChanged(skin, allowFallback); + base.SkinChanged(skin); if (Drawable is IHasText textDrawable) textDrawable.Text = Text; diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index d454e199dc..1338462dd6 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -73,9 +73,9 @@ namespace osu.Game.Skinning components.Remove(component); } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected override void SkinChanged(ISkinSource skin) { - base.SkinChanged(skin, allowFallback); + base.SkinChanged(skin); Reload(); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index fbdd27e762..672274a2ad 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -31,9 +31,9 @@ namespace osu.Game.Storyboards.Drawables [Resolved] private IBindable> mods { get; set; } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected override void SkinChanged(ISkinSource skin) { - base.SkinChanged(skin, allowFallback); + base.SkinChanged(skin); foreach (var mod in mods.Value.OfType()) { From b13b732e02e3efb0a7a58cf96b03e1bf7d9a0d05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 14:50:56 +0900 Subject: [PATCH 0311/1621] Remove incorrect `DefaultSkin` usage --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 3576b149bf..73a167b7aa 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps public bool SkinLoaded => skin.IsResultAvailable; public ISkin Skin => skin.Value; - protected virtual ISkin GetSkin() => new DefaultSkin(null); + protected virtual ISkin GetSkin() => null; private readonly RecyclableLazy skin; public abstract Stream GetStream(string storagePath); From c39ea857012ca12f6591b737cfb212cffdf71a4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 May 2021 15:39:35 +0900 Subject: [PATCH 0312/1621] Fix `TestSceneSkinnableSound` not doing DI correctly --- .../Visual/Gameplay/TestSceneSkinnableSound.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index d792405eeb..0e3d22ff1d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -29,14 +29,13 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("setup hierarchy", () => { - Children = new Drawable[] + Child = skinSource = new TestSkinSourceContainer { - skinSource = new TestSkinSourceContainer - { - RelativeSizeAxes = Axes.Both, - Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("Gameplay/normal-sliderslide")) - }, + RelativeSizeAxes = Axes.Both, }; + + // has to be added after the hierarchy above else the `ISkinSource` dependency won't be cached. + skinSource.Add(skinnableSound = new PausableSkinnableSound(new SampleInfo("Gameplay/normal-sliderslide"))); }); } From 0b17af81f186c2f248307d185fb760ec2688e2bf Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 27 May 2021 09:48:30 +0000 Subject: [PATCH 0313/1621] 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 0314/1621] 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 d2d089629881770193a5d2c42f7b4c915e0310d4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 May 2021 19:22:20 +0900 Subject: [PATCH 0315/1621] Expose GameplayBeatmap and GameplayRuleset from Player --- osu.Game/Screens/Play/Player.cs | 38 +++++++++++------------- osu.Game/Screens/Play/SpectatorPlayer.cs | 16 +++++----- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a9f3edf049..2258d509b7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -84,10 +84,6 @@ namespace osu.Game.Screens.Play [Resolved] private ScoreManager scoreManager { get; set; } - private RulesetInfo rulesetInfo; - - private Ruleset ruleset; - [Resolved] private IAPIProvider api { get; set; } @@ -97,6 +93,10 @@ namespace osu.Game.Screens.Play [Resolved] private SpectatorClient spectatorClient { get; set; } + protected Ruleset GameplayRuleset { get; private set; } + + protected GameplayBeatmap GameplayBeatmap { get; private set; } + private Sample sampleRestart; public BreakOverlay BreakOverlay; @@ -145,8 +145,6 @@ namespace osu.Game.Screens.Play Configuration = configuration ?? new PlayerConfiguration(); } - private GameplayBeatmap gameplayBeatmap; - private ScreenSuspensionHandler screenSuspension; private DependencyContainer dependencies; @@ -204,16 +202,16 @@ namespace osu.Game.Screens.Play if (game is OsuGame osuGame) LocalUserPlaying.BindTo(osuGame.LocalUserPlaying); - DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); + DrawableRuleset = GameplayRuleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); dependencies.CacheAs(DrawableRuleset); - ScoreProcessor = ruleset.CreateScoreProcessor(); + ScoreProcessor = GameplayRuleset.CreateScoreProcessor(); ScoreProcessor.ApplyBeatmap(playableBeatmap); ScoreProcessor.Mods.BindTo(Mods); dependencies.CacheAs(ScoreProcessor); - HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); + HealthProcessor = GameplayRuleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); HealthProcessor.ApplyBeatmap(playableBeatmap); dependencies.CacheAs(HealthProcessor); @@ -223,16 +221,16 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime); - AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap)); + AddInternal(GameplayBeatmap = new GameplayBeatmap(playableBeatmap)); AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); - dependencies.CacheAs(gameplayBeatmap); + dependencies.CacheAs(GameplayBeatmap); var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation // full access to all skin sources. - var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); + var rulesetSkinProvider = new SkinProvidingContainer(GameplayRuleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); // 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. @@ -247,7 +245,7 @@ namespace osu.Game.Screens.Play // also give the HUD a ruleset container to allow rulesets to potentially override HUD elements (used to disable combo counters etc.) // we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there. - var hudRulesetContainer = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); + var hudRulesetContainer = new SkinProvidingContainer(GameplayRuleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); // add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components. GameplayClockContainer.Add(hudRulesetContainer.WithChild(createOverlayComponents(Beatmap.Value))); @@ -284,7 +282,7 @@ namespace osu.Game.Screens.Play { HealthProcessor.ApplyResult(r); ScoreProcessor.ApplyResult(r); - gameplayBeatmap.ApplyResult(r); + GameplayBeatmap.ApplyResult(r); }; DrawableRuleset.RevertResult += r => @@ -473,18 +471,18 @@ namespace osu.Game.Screens.Play if (Beatmap.Value.Beatmap == null) throw new InvalidOperationException("Beatmap was not loaded"); - rulesetInfo = Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset; - ruleset = rulesetInfo.CreateInstance(); + var rulesetInfo = Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset; + GameplayRuleset = rulesetInfo.CreateInstance(); try { - playable = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Mods.Value); + playable = Beatmap.Value.GetPlayableBeatmap(GameplayRuleset.RulesetInfo, Mods.Value); } catch (BeatmapInvalidForRulesetException) { // A playable beatmap may not be creatable with the user's preferred ruleset, so try using the beatmap's default ruleset rulesetInfo = Beatmap.Value.BeatmapInfo.Ruleset; - ruleset = rulesetInfo.CreateInstance(); + GameplayRuleset = rulesetInfo.CreateInstance(); playable = Beatmap.Value.GetPlayableBeatmap(rulesetInfo, Mods.Value); } @@ -915,7 +913,7 @@ namespace osu.Game.Screens.Play ScoreInfo = new ScoreInfo { Beatmap = Beatmap.Value.BeatmapInfo, - Ruleset = rulesetInfo, + Ruleset = GameplayRuleset.RulesetInfo, Mods = Mods.Value.ToArray(), } }; @@ -951,7 +949,7 @@ namespace osu.Game.Screens.Play using (var stream = new MemoryStream()) { - new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream); + new LegacyScoreEncoder(score, GameplayBeatmap.PlayableBeatmap).Encode(stream); replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); } diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index a8125dfded..ad0f05e931 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -16,6 +16,9 @@ namespace osu.Game.Screens.Play { public class SpectatorPlayer : Player { + [Resolved] + private SpectatorClient spectatorClient { get; set; } + private readonly Score score; protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap @@ -25,14 +28,6 @@ namespace osu.Game.Screens.Play this.score = score; } - protected override ResultsScreen CreateResults(ScoreInfo score) - { - return new SpectatorResultsScreen(score); - } - - [Resolved] - private SpectatorClient spectatorClient { get; set; } - [BackgroundDependencyLoader] private void load() { @@ -48,6 +43,11 @@ namespace osu.Game.Screens.Play }); } + protected override ResultsScreen CreateResults(ScoreInfo score) + { + return new SpectatorResultsScreen(score); + } + protected override void PrepareReplay() { DrawableRuleset?.SetReplayScore(score); From 7f24518004854d6e3e0167ac7a42c0e629fcc801 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 May 2021 19:24:22 +0900 Subject: [PATCH 0316/1621] Fix spectator crashing when converting mania replay frames --- osu.Game/Screens/Play/SpectatorPlayer.cs | 37 ++++++++++++++++++++ osu.Game/Screens/Spectate/SpectatorScreen.cs | 29 --------------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index ad0f05e931..c5e26bdef6 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -9,6 +9,8 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Spectator; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Replays.Types; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -43,6 +45,36 @@ namespace osu.Game.Screens.Play }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + spectatorClient.OnNewFrames += userSentFrames; + } + + private void userSentFrames(int userId, FrameDataBundle bundle) + { + if (userId != score.ScoreInfo.User.Id) + return; + + if (!LoadedBeatmapSuccessfully) + return; + + if (!this.IsCurrentScreen()) + return; + + foreach (var frame in bundle.Frames) + { + IConvertibleReplayFrame convertibleFrame = GameplayRuleset.CreateConvertibleReplayFrame(); + convertibleFrame.FromLegacy(frame, GameplayBeatmap.PlayableBeatmap); + + var convertedFrame = (ReplayFrame)convertibleFrame; + convertedFrame.Time = frame.Time; + + score.Replay.Frames.Add(convertedFrame); + } + } + protected override ResultsScreen CreateResults(ScoreInfo score) { return new SpectatorResultsScreen(score); @@ -67,6 +99,8 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { spectatorClient.OnUserBeganPlaying -= userBeganPlaying; + spectatorClient.OnNewFrames -= userSentFrames; + return base.OnExiting(next); } @@ -85,7 +119,10 @@ namespace osu.Game.Screens.Play base.Dispose(isDisposing); if (spectatorClient != null) + { spectatorClient.OnUserBeganPlaying -= userBeganPlaying; + spectatorClient.OnNewFrames -= userSentFrames; + } } } } diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 9a20bb58b8..8fc9222f59 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -15,8 +15,6 @@ using osu.Game.Database; using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Rulesets; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Types; using osu.Game.Scoring; using osu.Game.Users; @@ -71,8 +69,6 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - spectatorClient.OnNewFrames += userSentFrames; - managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); @@ -197,29 +193,6 @@ namespace osu.Game.Screens.Spectate Schedule(() => StartGameplay(userId, gameplayState)); } - private void userSentFrames(int userId, FrameDataBundle bundle) - { - if (!userMap.ContainsKey(userId)) - return; - - if (!gameplayStates.TryGetValue(userId, out var gameplayState)) - return; - - // The ruleset instance should be guaranteed to be in sync with the score via ScoreLock. - Debug.Assert(gameplayState.Ruleset != null && gameplayState.Ruleset.RulesetInfo.Equals(gameplayState.Score.ScoreInfo.Ruleset)); - - foreach (var frame in bundle.Frames) - { - IConvertibleReplayFrame convertibleFrame = gameplayState.Ruleset.CreateConvertibleReplayFrame(); - convertibleFrame.FromLegacy(frame, gameplayState.Beatmap.Beatmap); - - var convertedFrame = (ReplayFrame)convertibleFrame; - convertedFrame.Time = frame.Time; - - gameplayState.Score.Replay.Frames.Add(convertedFrame); - } - } - /// /// Invoked when a spectated user's state has changed. /// @@ -260,8 +233,6 @@ namespace osu.Game.Screens.Spectate if (spectatorClient != null) { - spectatorClient.OnNewFrames -= userSentFrames; - foreach (var (userId, _) in userMap) spectatorClient.StopWatchingUser(userId); } From c787c008a50f3a7d623f0af37f49175ffcf71f04 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 31 May 2021 20:21:26 +0900 Subject: [PATCH 0317/1621] Fix test potentially not waiting for player to load --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index e9894ff469..9606d8c828 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.Gameplay private double currentFrameStableTime => player.ChildrenOfType().First().FrameStableClock.CurrentTime; - private void waitForPlayer() => AddUntilStep("wait for player", () => Stack.CurrentScreen is Player); + private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true); private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); From 4b27d43e26b3983bd7a60630f997f3317edecc26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 May 2021 15:13:56 +0900 Subject: [PATCH 0318/1621] Add new parameter for default fallback logic in `LegacySkin` --- osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index caf37e5bc9..6085eb1c37 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -18,7 +18,7 @@ namespace osu.Game.Skinning protected override bool UseCustomSampleBanks => true; public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore storage, IStorageResourceProvider resources) - : base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), resources, beatmap.Path) + : base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), resources, beatmap.Path, fallbackToDefault: false) { // Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer) Configuration.AllowDefaultComboColoursFallback = false; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index d3474caac9..908ed37b6a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -26,6 +26,8 @@ namespace osu.Game.Skinning { public class LegacySkin : Skin { + private readonly bool fallbackToDefault; + [CanBeNull] protected TextureStore Textures; @@ -54,16 +56,29 @@ namespace osu.Game.Skinning private readonly Dictionary maniaConfigurations = new Dictionary(); + private readonly DefaultLegacySkin legacyDefaultFallback; + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public LegacySkin(SkinInfo skin, IStorageResourceProvider resources) - : this(skin, new LegacySkinResourceStore(skin, resources.Files), resources, "skin.ini") + : this(skin, new LegacySkinResourceStore(skin, resources.Files), resources, "skin.ini", true) { } - protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore storage, [CanBeNull] IStorageResourceProvider resources, string filename) + /// + /// Construct a new legacy skin instance. + /// + /// The model for this skin. + /// A storage for looking up files within this skin using user-facing filenames. + /// Access to raw game resources. + /// The user-facing filename of the configuration file to be parsed. Can accept an .osu or skin.ini file. + /// Whether lookups should fallback to the implementations if not provided locally. + protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore storage, [CanBeNull] IStorageResourceProvider resources, string configurationFilename, bool fallbackToDefault = false) : base(skin, resources) { - using (var stream = storage?.GetStream(filename)) + this.fallbackToDefault = fallbackToDefault; + legacyDefaultFallback = new DefaultLegacySkin(storage, resources); + + using (var stream = storage?.GetStream(configurationFilename)) { if (stream != null) { From 1d30791ab0b8d747eea64b7ea4c5696c2945e1e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 May 2021 15:27:14 +0900 Subject: [PATCH 0319/1621] Add potential pathway for legacy lookups --- osu.Game/Skinning/LegacySkin.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 908ed37b6a..02d9c32281 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -142,7 +142,7 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookup maniaLookup: if (!AllowManiaSkin) - return null; + break; var result = lookupForMania(maniaLookup); if (result != null) @@ -157,7 +157,7 @@ namespace osu.Game.Skinning return genericLookup(lookup); } - return null; + return fallbackToDefault ? legacyDefaultFallback.GetConfig(lookup) : null; } private IBindable lookupForMania(LegacyManiaSkinConfigurationLookup maniaLookup) @@ -334,7 +334,7 @@ namespace osu.Game.Skinning { } - return null; + return fallbackToDefault ? legacyDefaultFallback.GetConfig(lookup) : null; } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -516,7 +516,7 @@ namespace osu.Game.Skinning return sample; } - return null; + return fallbackToDefault ? legacyDefaultFallback.GetSample(sampleInfo) : null; } private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) From 88ed95e012ec2ea9609f8959285a5135c43ef6bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 May 2021 17:04:38 +0900 Subject: [PATCH 0320/1621] Add `FindProvider` lookup function --- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 6 +++--- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 2 +- osu.Game/Skinning/ISkin.cs | 8 ++++++++ osu.Game/Skinning/LegacySkin.cs | 11 +++++++++++ osu.Game/Skinning/SkinProvidingContainer.cs | 8 ++++++++ 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 261b8b1fad..c137e2f7cb 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -69,10 +69,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private void sourceChanged() { - isLegacySkin = new Lazy(() => Source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); - hasKeyTexture = new Lazy(() => Source.GetAnimation( + isLegacySkin = new Lazy(() => Source.FindProvider(s => s.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null) != null); + hasKeyTexture = new Lazy(() => Source.FindProvider(s => s.GetAnimation( this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value - ?? "mania-key1", true, true) != null); + ?? "mania-key1", true, true) != null) != null); } public override Drawable GetDrawableComponent(ISkinComponent component) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index ffd4f78400..33dc59f30a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void sourceChanged() { - hasHitCircle = new Lazy(() => Source.GetTexture("hitcircle") != null); + hasHitCircle = new Lazy(Source.FindProvider(s => s.GetTexture("hitcircle") != null) != null); } public override Drawable GetDrawableComponent(ISkinComponent component) diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index 73f7cf6d39..df346556fd 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -57,5 +57,13 @@ namespace osu.Game.Skinning /// A matching value boxed in an , or null if unavailable. [CanBeNull] IBindable GetConfig(TLookup lookup); + + /// + /// For the specified texture, find any potential skin that can fulfill the lookup. + /// This should be used for cases where subsequent lookups (for related components) need to occur on the same skin. + /// + /// The skin to be used for subsequent lookups, or null if none is available. + [CanBeNull] + ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 02d9c32281..5ce2d74c99 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -556,5 +556,16 @@ namespace osu.Game.Skinning Textures?.Dispose(); Samples?.Dispose(); } + + ISkin ISkin.FindProvider(Func lookupFunction) + { + if (lookupFunction(this)) + return this; + + if (!fallbackToDefault) + return null; + + return (legacyDefaultFallback as ISkin)?.FindProvider(lookupFunction); + } } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index cf22b2e820..c183cd62df 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -41,6 +41,14 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } + public ISkin FindProvider(Func lookupFunction) + { + if (lookupFunction(skin)) + return skin; + + return fallbackSource.FindProvider(lookupFunction); + } + public Drawable GetDrawableComponent(ISkinComponent component) { Drawable sourceDrawable; From 8e489754cc4595a3bf95ec34b6024431af7b15f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 May 2021 17:09:30 +0900 Subject: [PATCH 0321/1621] Add ability for `LegacySkin`s to customise the fallback provider --- osu.Game/Skinning/DefaultLegacySkin.cs | 2 ++ osu.Game/Skinning/LegacySkin.cs | 26 ++++++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 30192182f3..1d17b5ce20 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -31,6 +31,8 @@ namespace osu.Game.Skinning Configuration.LegacyVersion = 2.7m; } + protected override DefaultLegacySkin CreateFallbackSkin(IResourceStore storage, IStorageResourceProvider resources) => null; + public static SkinInfo Info { get; } = new SkinInfo { ID = SkinInfo.CLASSIC_SKIN, // this is temporary until database storage is decided upon. diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5ce2d74c99..d249f63901 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -24,7 +24,7 @@ using osuTK.Graphics; namespace osu.Game.Skinning { - public class LegacySkin : Skin + public class LegacySkin : Skin, ISkin { private readonly bool fallbackToDefault; @@ -56,6 +56,7 @@ namespace osu.Game.Skinning private readonly Dictionary maniaConfigurations = new Dictionary(); + [CanBeNull] private readonly DefaultLegacySkin legacyDefaultFallback; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] @@ -71,12 +72,12 @@ namespace osu.Game.Skinning /// A storage for looking up files within this skin using user-facing filenames. /// Access to raw game resources. /// The user-facing filename of the configuration file to be parsed. Can accept an .osu or skin.ini file. - /// Whether lookups should fallback to the implementations if not provided locally. + /// Whether lookups via fallback to the implementations if not provided locally. protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore storage, [CanBeNull] IStorageResourceProvider resources, string configurationFilename, bool fallbackToDefault = false) : base(skin, resources) { this.fallbackToDefault = fallbackToDefault; - legacyDefaultFallback = new DefaultLegacySkin(storage, resources); + legacyDefaultFallback = CreateFallbackSkin(storage, resources); using (var stream = storage?.GetStream(configurationFilename)) { @@ -117,6 +118,10 @@ namespace osu.Game.Skinning true) != null); } + [CanBeNull] + protected virtual DefaultLegacySkin CreateFallbackSkin(IResourceStore storage, IStorageResourceProvider resources) => + new DefaultLegacySkin(storage, resources); + public override IBindable GetConfig(TLookup lookup) { switch (lookup) @@ -157,7 +162,7 @@ namespace osu.Game.Skinning return genericLookup(lookup); } - return fallbackToDefault ? legacyDefaultFallback.GetConfig(lookup) : null; + return legacyDefaultFallback?.GetConfig(lookup); } private IBindable lookupForMania(LegacyManiaSkinConfigurationLookup maniaLookup) @@ -334,7 +339,7 @@ namespace osu.Game.Skinning { } - return fallbackToDefault ? legacyDefaultFallback.GetConfig(lookup) : null; + return legacyDefaultFallback?.GetConfig(lookup); } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -434,7 +439,12 @@ namespace osu.Game.Skinning break; } - return this.GetAnimation(component.LookupName, false, false); + var animation = this.GetAnimation(component.LookupName, false, false); + + if (animation != null) + return animation; + + return legacyDefaultFallback?.GetDrawableComponent(component); } private Texture getParticleTexture(HitResult result) @@ -494,7 +504,7 @@ namespace osu.Game.Skinning return texture; } - return null; + return legacyDefaultFallback?.GetTexture(componentName, wrapModeS, wrapModeT); } public override ISample GetSample(ISampleInfo sampleInfo) @@ -516,7 +526,7 @@ namespace osu.Game.Skinning return sample; } - return fallbackToDefault ? legacyDefaultFallback.GetSample(sampleInfo) : null; + return legacyDefaultFallback?.GetSample(sampleInfo); } private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) From 3ff9f9c89de524d0ecbffafcc1c13fbe02223ce7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 May 2021 17:25:21 +0900 Subject: [PATCH 0322/1621] Make `FindProvider` non-default --- .../TestSceneCursorTrail.cs | 2 ++ .../TestSceneGameplayCursor.cs | 1 + .../TestSceneSkinFallbacks.cs | 1 + .../TestSceneHitObjectAccentColour.cs | 2 ++ .../Skinning/LegacySkinAnimationTest.cs | 1 + .../Skins/TestSceneSkinConfigurationLookup.cs | 3 ++ .../Gameplay/TestSceneSkinnableDrawable.cs | 6 ++++ .../Gameplay/TestSceneSkinnableSound.cs | 1 + osu.Game/Skinning/ISkin.cs | 3 +- osu.Game/Skinning/LegacyBeatmapSkin.cs | 12 ++++++- osu.Game/Skinning/LegacySkin.cs | 31 ++++++++----------- osu.Game/Skinning/LegacySkinTransformer.cs | 3 ++ osu.Game/Skinning/Skin.cs | 2 ++ osu.Game/Skinning/SkinManager.cs | 2 ++ 14 files changed, 50 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index 0ba97fac54..9997660c2d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -102,6 +102,8 @@ namespace osu.Game.Rulesets.Osu.Tests public IBindable GetConfig(TLookup lookup) => null; + public ISkin FindProvider(Func lookupFunction) => null; + public event Action SourceChanged { add { } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 9a77292aff..76111342f0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -113,6 +113,7 @@ namespace osu.Game.Rulesets.Osu.Tests public Drawable GetDrawableComponent(ISkinComponent component) => null; public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; public ISample GetSample(ISampleInfo sampleInfo) => null; + public ISkin FindProvider(Func lookupFunction) => null; public IBindable GetConfig(TLookup lookup) { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 6c6f05c5c5..fd523fffcb 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -166,6 +166,7 @@ namespace osu.Game.Rulesets.Osu.Tests public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default; public IBindable GetConfig(TLookup lookup) => null; + public ISkin FindProvider(Func lookupFunction) => null; public event Action SourceChanged; diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index 883791c35c..76e5437305 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -123,6 +123,8 @@ namespace osu.Game.Tests.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public ISkin FindProvider(Func lookupFunction) => null; + public IBindable GetConfig(TLookup lookup) { switch (lookup) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs index b08a228de3..e45b8f7dc5 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs @@ -61,6 +61,7 @@ namespace osu.Game.Tests.NonVisual.Skinning public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException(); public ISample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException(); public IBindable GetConfig(TLookup lookup) => throw new NotSupportedException(); + public ISkin FindProvider(Func lookupFunction) => null; } private class TestAnimationTimeReference : IAnimationTimeReference diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index 732a3f3f42..c15d804a19 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.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.IO; using System.Linq; @@ -222,6 +223,8 @@ namespace osu.Game.Tests.Skins public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); + + public ISkin FindProvider(Func lookupFunction) => skin.FindProvider(lookupFunction); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 7a6e2f54c2..bb45d568de 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -301,6 +301,8 @@ namespace osu.Game.Tests.Visual.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); + + public ISkin FindProvider(Func lookupFunction) => throw new NotImplementedException(); } private class SecondarySource : ISkin @@ -312,6 +314,8 @@ namespace osu.Game.Tests.Visual.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); + + public ISkin FindProvider(Func lookupFunction) => throw new NotImplementedException(); } [Cached(typeof(ISkinSource))] @@ -325,6 +329,8 @@ namespace osu.Game.Tests.Visual.Gameplay public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); + public ISkin FindProvider(Func lookupFunction) => throw new NotImplementedException(); + public event Action SourceChanged { add { } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index d792405eeb..d69395fbaa 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -147,6 +147,7 @@ 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 void TriggerSourceChanged() { diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index df346556fd..1c3598abb4 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.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 JetBrains.Annotations; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -64,6 +65,6 @@ namespace osu.Game.Skinning /// /// The skin to be used for subsequent lookups, or null if none is available. [CanBeNull] - ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; + ISkin FindProvider(Func lookupFunction); } } diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 6085eb1c37..0d6608f579 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.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.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -18,7 +19,7 @@ namespace osu.Game.Skinning protected override bool UseCustomSampleBanks => true; public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore storage, IStorageResourceProvider resources) - : base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), resources, beatmap.Path, fallbackToDefault: false) + : base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), resources, beatmap.Path) { // Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer) Configuration.AllowDefaultComboColoursFallback = false; @@ -70,6 +71,15 @@ namespace osu.Game.Skinning return base.GetSample(sampleInfo); } + public override ISkin FindProvider(Func lookupFunction) + { + if (lookupFunction(this)) + return this; + + // beatmap skins don't do lookups on the default skin. this allows fallback to user / game default skins. + return null; + } + private static SkinInfo createSkinInfo(BeatmapInfo beatmap) => new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata?.AuthorString }; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index d249f63901..856e795dc6 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -24,10 +24,8 @@ using osuTK.Graphics; namespace osu.Game.Skinning { - public class LegacySkin : Skin, ISkin + public class LegacySkin : Skin { - private readonly bool fallbackToDefault; - [CanBeNull] protected TextureStore Textures; @@ -61,7 +59,7 @@ namespace osu.Game.Skinning [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public LegacySkin(SkinInfo skin, IStorageResourceProvider resources) - : this(skin, new LegacySkinResourceStore(skin, resources.Files), resources, "skin.ini", true) + : this(skin, new LegacySkinResourceStore(skin, resources.Files), resources, "skin.ini") { } @@ -72,11 +70,9 @@ namespace osu.Game.Skinning /// A storage for looking up files within this skin using user-facing filenames. /// Access to raw game resources. /// The user-facing filename of the configuration file to be parsed. Can accept an .osu or skin.ini file. - /// Whether lookups via fallback to the implementations if not provided locally. - protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore storage, [CanBeNull] IStorageResourceProvider resources, string configurationFilename, bool fallbackToDefault = false) + protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore storage, [CanBeNull] IStorageResourceProvider resources, string configurationFilename) : base(skin, resources) { - this.fallbackToDefault = fallbackToDefault; legacyDefaultFallback = CreateFallbackSkin(storage, resources); using (var stream = storage?.GetStream(configurationFilename)) @@ -529,6 +525,16 @@ namespace osu.Game.Skinning return legacyDefaultFallback?.GetSample(sampleInfo); } + public override ISkin FindProvider(Func lookupFunction) + { + var source = base.FindProvider(lookupFunction); + + if (source != null) + return source; + + return legacyDefaultFallback?.FindProvider(lookupFunction); + } + private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) { var lookupNames = hitSample.LookupNames.SelectMany(getFallbackNames); @@ -566,16 +572,5 @@ namespace osu.Game.Skinning Textures?.Dispose(); Samples?.Dispose(); } - - ISkin ISkin.FindProvider(Func lookupFunction) - { - if (lookupFunction(this)) - return this; - - if (!fallbackToDefault) - return null; - - return (legacyDefaultFallback as ISkin)?.FindProvider(lookupFunction); - } } } diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index ae8faf1a3b..cace4acf6c 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.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.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -47,5 +48,7 @@ namespace osu.Game.Skinning } public abstract IBindable GetConfig(TLookup lookup); + + public ISkin FindProvider(Func lookupFunction) => Source.FindProvider(lookupFunction); } } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index b6cb8fc7a4..c12e9a64c2 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -35,6 +35,8 @@ namespace osu.Game.Skinning public abstract IBindable GetConfig(TLookup lookup); + public virtual ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; + protected Skin(SkinInfo skin, IStorageResourceProvider resources) { SkinInfo = skin; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 079c537066..fa4f657882 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -212,6 +212,8 @@ namespace osu.Game.Skinning public IBindable GetConfig(TLookup lookup) => CurrentSkin.Value.GetConfig(lookup); + public ISkin FindProvider(Func lookupFunction) => CurrentSkin.Value.FindProvider(lookupFunction); + #region IResourceStorageProvider AudioManager IStorageResourceProvider.AudioManager => audio; From 282c5a917786cc99d08a178356ed971f039fbb35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 May 2021 18:00:06 +0900 Subject: [PATCH 0323/1621] Fix potential nullref in `SkinProvidingContainer` --- osu.Game/Skinning/LegacySkin.cs | 6 +++--- osu.Game/Skinning/SkinProvidingContainer.cs | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 856e795dc6..92944a9c93 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -73,7 +73,8 @@ namespace osu.Game.Skinning protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore storage, [CanBeNull] IStorageResourceProvider resources, string configurationFilename) : base(skin, resources) { - legacyDefaultFallback = CreateFallbackSkin(storage, resources); + if (resources != null) + legacyDefaultFallback = CreateFallbackSkin(storage, resources); using (var stream = storage?.GetStream(configurationFilename)) { @@ -115,8 +116,7 @@ namespace osu.Game.Skinning } [CanBeNull] - protected virtual DefaultLegacySkin CreateFallbackSkin(IResourceStore storage, IStorageResourceProvider resources) => - new DefaultLegacySkin(storage, resources); + protected virtual DefaultLegacySkin CreateFallbackSkin(IResourceStore storage, IStorageResourceProvider resources) => new DefaultLegacySkin(resources); public override IBindable GetConfig(TLookup lookup) { diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index c183cd62df..863b5f5a24 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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -20,8 +21,10 @@ namespace osu.Game.Skinning { public event Action SourceChanged; + [CanBeNull] private readonly ISkin skin; + [CanBeNull] private ISkinSource fallbackSource; protected virtual bool AllowDrawableLookup(ISkinComponent component) => true; @@ -43,10 +46,10 @@ namespace osu.Game.Skinning public ISkin FindProvider(Func lookupFunction) { - if (lookupFunction(skin)) + if (skin != null && lookupFunction(skin)) return skin; - return fallbackSource.FindProvider(lookupFunction); + return fallbackSource?.FindProvider(lookupFunction); } public Drawable GetDrawableComponent(ISkinComponent component) @@ -93,7 +96,7 @@ namespace osu.Game.Skinning { if (canUseSkinLookup) { - var bindable = skin.GetConfig(lookup); + var bindable = skin?.GetConfig(lookup); if (bindable != null) return bindable; } From 1161378b6bec0fd430a3734a0d5e852fec41298e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 May 2021 20:15:26 +0900 Subject: [PATCH 0324/1621] Fix incorrect fallback logic in `LegacyBeatmapSkin` --- osu.Game/Skinning/LegacyBeatmapSkin.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 0d6608f579..2374cb976b 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.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.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -71,12 +70,11 @@ namespace osu.Game.Skinning return base.GetSample(sampleInfo); } - public override ISkin FindProvider(Func lookupFunction) + protected override DefaultLegacySkin CreateFallbackSkin(IResourceStore storage, IStorageResourceProvider resources) { - if (lookupFunction(this)) - return this; - - // beatmap skins don't do lookups on the default skin. this allows fallback to user / game default skins. + // for simplicity, beatmap skins don't do lookups on the default skin. + // this will mean that fallback always occurs to the user (then default) skin. + // this may not offer perfect behaviour, but helps keep things simple. return null; } From 33577cbad5fe2f3a262877879ffdd9dcafc7753b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 May 2021 20:43:01 +0900 Subject: [PATCH 0325/1621] Fix multiple issues with default lookups --- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 4 ++-- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index c137e2f7cb..8aa0c85433 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -69,8 +69,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private void sourceChanged() { - isLegacySkin = new Lazy(() => Source.FindProvider(s => s.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null) != null); - hasKeyTexture = new Lazy(() => Source.FindProvider(s => s.GetAnimation( + isLegacySkin = new Lazy(() => FindProvider(s => s.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null) != null); + hasKeyTexture = new Lazy(() => FindProvider(s => s.GetAnimation( this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value ?? "mania-key1", true, true) != null) != null); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 33dc59f30a..33693748d9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void sourceChanged() { - hasHitCircle = new Lazy(Source.FindProvider(s => s.GetTexture("hitcircle") != null) != null); + hasHitCircle = new Lazy(() => FindProvider(s => s.GetTexture("hitcircle") != null) != null); } public override Drawable GetDrawableComponent(ISkinComponent component) From 69c4ccad05297772db8bc7cabc5dae759f789d92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 May 2021 21:27:11 +0900 Subject: [PATCH 0326/1621] Fix weird taiko logic failing for weird reasons that probably should not have been a thing --- osu.Game/Skinning/LegacySkin.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 92944a9c93..f3f3e67eef 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -563,7 +563,11 @@ namespace osu.Game.Skinning // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). string lastPiece = componentName.Split('/').Last(); - yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; + + if (componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal)) + yield return "taiko-" + lastPiece; + + yield return lastPiece; } protected override void Dispose(bool isDisposing) From 9221213fe527bdc4e33188a1a9a7642edd240ab4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 14:19:13 +0900 Subject: [PATCH 0327/1621] Fix potential nullref is beatmap load failed --- osu.Game/Screens/Play/ReplayPlayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 07c3d197da..f26675cc64 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -36,6 +36,8 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { + if (!LoadedBeatmapSuccessfully) return; + Score = createScore(GameplayBeatmap, Mods.Value); } From cbf3ef5400bbc7ea2e75f532845175f99a002341 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 14:22:16 +0900 Subject: [PATCH 0328/1621] Create replay via the `ICreateReplay` interface instead of explicitly `ModAutoplay` --- osu.Game/Screens/Select/PlaySongSelect.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 357222c109..418cf23ce7 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -111,9 +111,9 @@ namespace osu.Game.Screens.Select Player createPlayer() { - var autoplayMod = Mods.Value.OfType().FirstOrDefault(); - if (autoplayMod != null) - return new ReplayPlayer((beatmap, mods) => autoplayMod.CreateReplayScore(beatmap, mods)); + var replayGeneratingMod = Mods.Value.OfType().FirstOrDefault(); + if (replayGeneratingMod != null) + return new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods)); return new SoloPlayer(); } From 240f7facba9c365d1d3850f314ce2c4e34a24254 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 15:39:02 +0900 Subject: [PATCH 0329/1621] Add local concessions for autoplay test handling --- osu.Game/Tests/Visual/TestPlayer.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 0addc9de75..99308f8d75 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; @@ -48,6 +49,20 @@ namespace osu.Game.Tests.Visual PauseOnFocusLost = pauseOnFocusLost; } + protected override void PrepareReplay() + { + var replayGeneratingMod = Mods.Value.OfType().FirstOrDefault(); + + if (replayGeneratingMod != null) + { + // This logic should really not exist (and tests should be instantiating a ReplayPlayer), but a lot of base work is required to make that happen. + DrawableRuleset?.SetReplayScore(replayGeneratingMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value)); + return; + } + + base.PrepareReplay(); + } + [BackgroundDependencyLoader] private void load() { From 3ba0d29108fc250e9c29ea37ec14a9a900741ebf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 15:44:24 +0900 Subject: [PATCH 0330/1621] Fix incorrect beatmap being parsed down for autoplay generation --- osu.Game/Screens/Play/ReplayPlayer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index f26675cc64..91236ad607 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -17,7 +18,7 @@ namespace osu.Game.Screens.Play { protected Score Score { get; private set; } - private readonly Func, Score> createScore; + private readonly Func, Score> createScore; // Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108) protected override bool CheckModsAllowFailure() => false; @@ -27,7 +28,7 @@ namespace osu.Game.Screens.Play { } - public ReplayPlayer(Func, Score> createScore, PlayerConfiguration configuration = null) + public ReplayPlayer(Func, Score> createScore, PlayerConfiguration configuration = null) : base(configuration) { this.createScore = createScore; @@ -38,7 +39,7 @@ namespace osu.Game.Screens.Play { if (!LoadedBeatmapSuccessfully) return; - Score = createScore(GameplayBeatmap, Mods.Value); + Score = createScore(GameplayBeatmap.PlayableBeatmap, Mods.Value); } protected override void PrepareReplay() From 790f1dacc9839d0267797821bb59042c884b549d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 16:24:38 +0900 Subject: [PATCH 0331/1621] Ensure `ScoreProcessor` is still hooked up in special case --- osu.Game/Tests/Visual/TestPlayer.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 99308f8d75..b7e1c68c89 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -51,12 +51,16 @@ namespace osu.Game.Tests.Visual protected override void PrepareReplay() { - var replayGeneratingMod = Mods.Value.OfType().FirstOrDefault(); + var autoplayMod = Mods.Value.OfType().FirstOrDefault(); - if (replayGeneratingMod != null) + // This logic should really not exist (and tests should be instantiating a ReplayPlayer), but a lot of base work is required to make that happen. + if (autoplayMod != null) { - // This logic should really not exist (and tests should be instantiating a ReplayPlayer), but a lot of base work is required to make that happen. - DrawableRuleset?.SetReplayScore(replayGeneratingMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value)); + var replayScore = autoplayMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value); + + DrawableRuleset?.SetReplayScore(replayScore); + + ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(replayScore.ScoreInfo); return; } From 6e861a9b7fcb107e2f12887ee3f4b93e253beedd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 16:24:38 +0900 Subject: [PATCH 0332/1621] Revert incorrect `ScoreProcessor` change --- osu.Game/Tests/Visual/TestPlayer.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index b7e1c68c89..ceb886f9c4 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -56,11 +56,7 @@ namespace osu.Game.Tests.Visual // This logic should really not exist (and tests should be instantiating a ReplayPlayer), but a lot of base work is required to make that happen. if (autoplayMod != null) { - var replayScore = autoplayMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value); - - DrawableRuleset?.SetReplayScore(replayScore); - - ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(replayScore.ScoreInfo); + DrawableRuleset?.SetReplayScore(autoplayMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value)); return; } From 7a71cc1e821e0346e1d9c19fc4506388928b9ee3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 16:54:29 +0900 Subject: [PATCH 0333/1621] Fix actually incorrect navigation test (can no longer retry from autoplay results) --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 253e448bb4..dd05ce9b7e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); - AddStep("set autoplay", () => Game.SelectedMods.Value = new[] { new OsuModAutoplay() }); + AddStep("set nofail", () => Game.SelectedMods.Value = new[] { new OsuModNoFail() }); AddStep("press enter", () => InputManager.Key(Key.Enter)); AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); From ff815cb4b4f461f55d6154682ccd5ce0213ce80d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 16:57:40 +0900 Subject: [PATCH 0334/1621] Fix incorrect xmldoc --- osu.Game/Skinning/ISkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index 1c3598abb4..09e79a5ff5 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -60,7 +60,7 @@ namespace osu.Game.Skinning IBindable GetConfig(TLookup lookup); /// - /// For the specified texture, find any potential skin that can fulfill the lookup. + /// Find the first (if any) skin that can fulfill the lookup. /// This should be used for cases where subsequent lookups (for related components) need to occur on the same skin. /// /// The skin to be used for subsequent lookups, or null if none is available. From df0a5689e462c31046a917ac3f44e57505becdc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 17:13:13 +0900 Subject: [PATCH 0335/1621] Revert "Fix weird taiko logic failing for weird reasons that probably should not have been a thing" This reverts commit 69c4ccad05297772db8bc7cabc5dae759f789d92. --- osu.Game/Skinning/LegacySkin.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f3f3e67eef..92944a9c93 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -563,11 +563,7 @@ namespace osu.Game.Skinning // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). string lastPiece = componentName.Split('/').Last(); - - if (componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal)) - yield return "taiko-" + lastPiece; - - yield return lastPiece; + yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; } protected override void Dispose(bool isDisposing) From f14c0eae9956641f8c77179f165b059abc98914d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 17:28:41 +0900 Subject: [PATCH 0336/1621] Fix editor no longer creating autoplay frames --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index a2dade2627..0667145ffb 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -349,6 +349,9 @@ namespace osu.Game.Rulesets.UI foreach (var mod in mods.OfType>()) mod.ApplyToDrawableRuleset(this); + foreach (var mod in mods.OfType()) + SetReplayScore(mod.CreateReplayScore(Beatmap, mods)); + foreach (var mod in mods.OfType()) mod.ReadFromConfig(config); } From 83bfd36498dcb30d2ad8e22184ab56504a9968d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 17:56:12 +0900 Subject: [PATCH 0337/1621] Disable broken taiko hitsound fallback tests for now --- .../TestSceneTaikoHitObjectSamples.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs index 221d715a35..7318a6d929 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestCase("taiko-normal-hitnormal")] [TestCase("normal-hitnormal")] - [TestCase("hitnormal")] + // [TestCase("hitnormal")] intentionally broken (will play classic default instead). public void TestDefaultCustomSampleFromBeatmap(string expectedSample) { SetupSkins(expectedSample, expectedSample); @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestCase("taiko-normal-hitnormal")] [TestCase("normal-hitnormal")] - [TestCase("hitnormal")] + // [TestCase("hitnormal")] intentionally broken (will play classic default instead). public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) { SetupSkins(string.Empty, expectedSample); From a837fc9e3ba99c315e3fd29c13692c7c159965c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 18:00:24 +0900 Subject: [PATCH 0338/1621] Remove duplicated taiko fallback --- osu.Game/Skinning/LegacySkin.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 92944a9c93..98cc5c8fd8 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -562,8 +562,7 @@ namespace osu.Game.Skinning yield return componentName; // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). - string lastPiece = componentName.Split('/').Last(); - yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; + yield return componentName.Split('/').Last(); } protected override void Dispose(bool isDisposing) From ea4644be905f602ae2dd9ed068808f568b900363 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 17:13:13 +0900 Subject: [PATCH 0339/1621] Revert "Fix weird taiko logic failing for weird reasons that probably should not have been a thing" This reverts commit 69c4ccad05297772db8bc7cabc5dae759f789d92. --- osu.Game/Skinning/LegacySkin.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f3f3e67eef..92944a9c93 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -563,11 +563,7 @@ namespace osu.Game.Skinning // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). string lastPiece = componentName.Split('/').Last(); - - if (componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal)) - yield return "taiko-" + lastPiece; - - yield return lastPiece; + yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; } protected override void Dispose(bool isDisposing) From dd006401b3d1bcbe41f56b907178ba4d42a691ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 17:56:12 +0900 Subject: [PATCH 0340/1621] Disable broken taiko hitsound fallback tests for now --- .../TestSceneTaikoHitObjectSamples.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs index 221d715a35..7318a6d929 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestCase("taiko-normal-hitnormal")] [TestCase("normal-hitnormal")] - [TestCase("hitnormal")] + // [TestCase("hitnormal")] intentionally broken (will play classic default instead). public void TestDefaultCustomSampleFromBeatmap(string expectedSample) { SetupSkins(expectedSample, expectedSample); @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestCase("taiko-normal-hitnormal")] [TestCase("normal-hitnormal")] - [TestCase("hitnormal")] + // [TestCase("hitnormal")] intentionally broken (will play classic default instead). public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) { SetupSkins(string.Empty, expectedSample); From 3a6d081d82c20fc587a529783c1495b662d2d8c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 18:00:24 +0900 Subject: [PATCH 0341/1621] Remove duplicated taiko fallback --- osu.Game/Skinning/LegacySkin.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 92944a9c93..98cc5c8fd8 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -562,8 +562,7 @@ namespace osu.Game.Skinning yield return componentName; // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). - string lastPiece = componentName.Split('/').Last(); - yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; + yield return componentName.Split('/').Last(); } protected override void Dispose(bool isDisposing) From 54338bdcc5666a60395ee478a817d4aa145a7a18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 18:29:15 +0900 Subject: [PATCH 0342/1621] Add test ensuring correct osu! ruleset sample lookups --- ...u-hitobject-beatmap-custom-sample-bank.osu | 10 ++++ .../TestSceneOsuHitObjectSamples.cs | 49 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/SampleLookups/osu-hitobject-beatmap-custom-sample-bank.osu create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneOsuHitObjectSamples.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/SampleLookups/osu-hitobject-beatmap-custom-sample-bank.osu b/osu.Game.Rulesets.Osu.Tests/Resources/SampleLookups/osu-hitobject-beatmap-custom-sample-bank.osu new file mode 100644 index 0000000000..a84fc08bb8 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Resources/SampleLookups/osu-hitobject-beatmap-custom-sample-bank.osu @@ -0,0 +1,10 @@ +osu file format v14 + +[General] +Mode: 0 + +[TimingPoints] +0,300,4,1,2,100,1,0 + +[HitObjects] +444,320,1000,5,0,0:0:0:0: diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHitObjectSamples.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHitObjectSamples.cs new file mode 100644 index 0000000000..e8d98ce3b8 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHitObjectSamples.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Reflection; +using NUnit.Framework; +using osu.Framework.IO.Stores; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneOsuHitObjectSamples : HitObjectSampleTest + { + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + + protected override IResourceStore RulesetResources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneOsuHitObjectSamples))); + + [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] + public void TestDefaultCustomSampleFromBeatmap(string expectedSample) + { + SetupSkins(expectedSample, expectedSample); + + CreateTestWithBeatmap("osu-hitobject-beatmap-custom-sample-bank.osu"); + + AssertBeatmapLookup(expectedSample); + } + + [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] + public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) + { + SetupSkins(string.Empty, expectedSample); + + CreateTestWithBeatmap("osu-hitobject-beatmap-custom-sample-bank.osu"); + + AssertUserLookup(expectedSample); + } + + [TestCase("normal-hitnormal2")] + public void TestUserSkinLookupIgnoresSampleBank(string unwantedSample) + { + SetupSkins(string.Empty, unwantedSample); + + CreateTestWithBeatmap("osu-hitobject-beatmap-custom-sample-bank.osu"); + + AssertNoLookup(unwantedSample); + } + } +} From 2e2281c7d22345164d314464341138d3d544f250 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 18:56:22 +0900 Subject: [PATCH 0343/1621] Revert disabling taiko sample tests and fix logic --- .../TestSceneTaikoHitObjectSamples.cs | 4 ++-- .../Legacy/TaikoLegacySkinTransformer.cs | 23 +++++++++++-------- osu.Game/Skinning/LegacySkin.cs | 2 ++ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs index 7318a6d929..221d715a35 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestCase("taiko-normal-hitnormal")] [TestCase("normal-hitnormal")] - // [TestCase("hitnormal")] intentionally broken (will play classic default instead). + [TestCase("hitnormal")] public void TestDefaultCustomSampleFromBeatmap(string expectedSample) { SetupSkins(expectedSample, expectedSample); @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestCase("taiko-normal-hitnormal")] [TestCase("normal-hitnormal")] - // [TestCase("hitnormal")] intentionally broken (will play classic default instead). + [TestCase("hitnormal")] public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) { SetupSkins(string.Empty, expectedSample); diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index e0557c8617..7ce0f6b93b 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -152,32 +152,35 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy throw new ArgumentOutOfRangeException(nameof(component), $"Invalid component type: {component}"); } - public override ISample GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo)); + public override ISample GetSample(ISampleInfo sampleInfo) + { + if (sampleInfo is HitSampleInfo hitSampleInfo) + return Source.GetSample(new LegacyTaikoSampleInfo(hitSampleInfo)); + + return base.GetSample(sampleInfo); + } public override IBindable GetConfig(TLookup lookup) => Source.GetConfig(lookup); - private class LegacyTaikoSampleInfo : ISampleInfo + private class LegacyTaikoSampleInfo : HitSampleInfo { - private readonly ISampleInfo source; + public LegacyTaikoSampleInfo(HitSampleInfo sampleInfo) + : base(sampleInfo.Name, sampleInfo.Bank, sampleInfo.Suffix, sampleInfo.Volume) - public LegacyTaikoSampleInfo(ISampleInfo source) { - this.source = source; } - public IEnumerable LookupNames + public override IEnumerable LookupNames { get { - foreach (var name in source.LookupNames) + foreach (var name in base.LookupNames) yield return name.Insert(name.LastIndexOf('/') + 1, "taiko-"); - foreach (var name in source.LookupNames) + foreach (var name in base.LookupNames) yield return name; } } - - public int Volume => source.Volume; } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 98cc5c8fd8..474ac1a794 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -519,7 +519,9 @@ namespace osu.Game.Skinning var sample = Samples?.Get(lookup); if (sample != null) + { return sample; + } } return legacyDefaultFallback?.GetSample(sampleInfo); From 8a76d97b63a65fe4e303525e4bd09d809cafa2f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Jun 2021 11:06:30 +0900 Subject: [PATCH 0344/1621] Remove replay logic from `DrawableRuleset` (and implement in `DrawableEditorRulesetWrapper`) --- .../Rulesets/Edit/DrawableEditorRulesetWrapper.cs | 14 +++++++++++--- osu.Game/Rulesets/UI/DrawableRuleset.cs | 12 +----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs index 8166e6b8ce..071f01ca00 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.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.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; @@ -52,15 +54,21 @@ namespace osu.Game.Rulesets.Edit if (changeHandler != null) { // for now only regenerate replay on a finalised state change, not HitObjectUpdated. - changeHandler.OnStateChange += updateReplay; + changeHandler.OnStateChange += () => Scheduler.AddOnce(regenerateAutoplay); } else { - beatmap.HitObjectUpdated += _ => updateReplay(); + beatmap.HitObjectUpdated += _ => Scheduler.AddOnce(regenerateAutoplay); } + + Scheduler.AddOnce(regenerateAutoplay); } - private void updateReplay() => Scheduler.AddOnce(drawableRuleset.RegenerateAutoplay); + private void regenerateAutoplay() + { + var autoplayMod = drawableRuleset.Mods.OfType().Single(); + drawableRuleset.SetReplayScore(autoplayMod.CreateReplayScore(drawableRuleset.Beatmap, drawableRuleset.Mods)); + } private void addHitObject(HitObject hitObject) { diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 0667145ffb..16a411d478 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -182,18 +182,11 @@ namespace osu.Game.Rulesets.UI .WithChild(ResumeOverlay))); } - RegenerateAutoplay(); + applyRulesetMods(Mods, config); loadObjects(cancellationToken ?? default); } - public void RegenerateAutoplay() - { - // for now this is applying mods which aren't just autoplay. - // we'll need to reconsider this flow in the future. - applyRulesetMods(Mods, config); - } - /// /// Creates and adds drawable representations of hit objects to the play field. /// @@ -349,9 +342,6 @@ namespace osu.Game.Rulesets.UI foreach (var mod in mods.OfType>()) mod.ApplyToDrawableRuleset(this); - foreach (var mod in mods.OfType()) - SetReplayScore(mod.CreateReplayScore(Beatmap, mods)); - foreach (var mod in mods.OfType()) mod.ReadFromConfig(config); } From 911256603bf0ba6d83c05694c6d20ecdbafa041b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Jun 2021 11:10:02 +0900 Subject: [PATCH 0345/1621] Rewrite comment to hopefully be more informative --- osu.Game/Tests/Visual/TestPlayer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index ceb886f9c4..09da4db952 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -51,9 +51,15 @@ namespace osu.Game.Tests.Visual protected override void PrepareReplay() { + // Generally, replay generation is handled by whatever is constructing the player. + // This is implemented locally here to ease migration of test scenes that have some executions + // running with autoplay and some not, but are not written in a way that lends to instantiating + // different `Player` types. + // + // Eventually we will want to remove this and update all test usages which rely on autoplay to use + // a `TestReplayPlayer`. var autoplayMod = Mods.Value.OfType().FirstOrDefault(); - // This logic should really not exist (and tests should be instantiating a ReplayPlayer), but a lot of base work is required to make that happen. if (autoplayMod != null) { DrawableRuleset?.SetReplayScore(autoplayMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value)); From 45984f035b731ff0d39510b22df7375455fafc39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Jun 2021 11:33:43 +0900 Subject: [PATCH 0346/1621] Make autoplay tests test via the `ReplayPlayer` code path --- .../Visual/Gameplay/TestSceneAutoplay.cs | 4 +- osu.Game/Tests/Visual/TestReplayPlayer.cs | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Tests/Visual/TestReplayPlayer.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index e47c782bca..bc7cf8eee2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -18,12 +18,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Description("Player instantiated with an autoplay mod.")] public class TestSceneAutoplay : TestSceneAllRulesetPlayers { - protected new TestPlayer Player => (TestPlayer)base.Player; + protected new TestReplayPlayer Player => (TestReplayPlayer)base.Player; protected override Player CreatePlayer(Ruleset ruleset) { SelectedMods.Value = new[] { ruleset.GetAutoplayMod() }; - return new TestPlayer(false); + return new TestReplayPlayer(false); } protected override void AddCheckSteps() diff --git a/osu.Game/Tests/Visual/TestReplayPlayer.cs b/osu.Game/Tests/Visual/TestReplayPlayer.cs new file mode 100644 index 0000000000..ac47d186eb --- /dev/null +++ b/osu.Game/Tests/Visual/TestReplayPlayer.cs @@ -0,0 +1,75 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + /// + /// A player that exposes many components that would otherwise not be available, for testing purposes. + /// + public class TestReplayPlayer : ReplayPlayer + { + protected override bool PauseOnFocusLost { get; } + + public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; + + /// + /// Mods from *player* (not OsuScreen). + /// + public new Bindable> Mods => base.Mods; + + public new HUDOverlay HUDOverlay => base.HUDOverlay; + + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public new HealthProcessor HealthProcessor => base.HealthProcessor; + + public new bool PauseCooldownActive => base.PauseCooldownActive; + + public readonly List Results = new List(); + + /// + /// Instantiate a replay player that renders an autoplay mod. + /// + public TestReplayPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) + : base((beatmap, mods) => mods.OfType().First().CreateReplayScore(beatmap, mods), new PlayerConfiguration + { + AllowPause = allowPause, + ShowResults = showResults + }) + { + PauseOnFocusLost = pauseOnFocusLost; + } + + /// + /// Instantiate a replay player that renders the provided replay. + /// + public TestReplayPlayer(Score score, bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) + : base(score, new PlayerConfiguration + { + AllowPause = allowPause, + ShowResults = showResults + }) + { + PauseOnFocusLost = pauseOnFocusLost; + } + + [BackgroundDependencyLoader] + private void load() + { + ScoreProcessor.NewJudgement += r => Results.Add(r); + } + } +} From a15cac6f5386651adf0af91d990f94525da98b99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Jun 2021 15:44:04 +0900 Subject: [PATCH 0347/1621] Change the way `Score` is initialised in `Player` to better lend to population of metadata --- .../Spectate/MultiSpectatorPlayer.cs | 4 +- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 8 +- osu.Game/Screens/Play/Player.cs | 73 ++++++++----------- osu.Game/Screens/Play/ReplayPlayer.cs | 24 +----- osu.Game/Screens/Play/SpectatorPlayer.cs | 12 +-- 5 files changed, 45 insertions(+), 76 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 0fe9e01d9d..2c157b0564 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -17,7 +17,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public class MultiSpectatorPlayer : SpectatorPlayer { private readonly Bindable waitingOnFrames = new Bindable(true); - private readonly Score score; private readonly ISpectatorPlayerClock spectatorPlayerClock; /// @@ -28,7 +27,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public MultiSpectatorPlayer([NotNull] Score score, [NotNull] ISpectatorPlayerClock spectatorPlayerClock) : base(score) { - this.score = score; this.spectatorPlayerClock = spectatorPlayerClock; } @@ -43,7 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate base.UpdateAfterChildren(); // This is required because the frame stable clock is set to WaitingOnFrames = false for one frame. - waitingOnFrames.Value = DrawableRuleset.FrameStableClock.WaitingOnFrames.Value || score.Replay.Frames.Count == 0; + waitingOnFrames.Value = DrawableRuleset.FrameStableClock.WaitingOnFrames.Value || Score.Replay.Frames.Count == 0; } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 260d4961ff..a2ef715367 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -54,11 +54,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists return new PlaylistsResultsScreen(score, RoomId.Value.Value, PlaylistItem, true); } - protected override Score CreateScore() + protected override void PrepareScoreForResults() { - var score = base.CreateScore(); - score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore()); - return score; + base.PrepareScoreForResults(); + + Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore()); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 99570f6c5e..0d1ebd30fc 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -24,10 +23,8 @@ using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Overlays; -using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -137,6 +134,8 @@ namespace osu.Game.Screens.Play public readonly PlayerConfiguration Configuration; + protected Score Score { get; private set; } + /// /// Create a new player instance. /// @@ -161,22 +160,32 @@ namespace osu.Game.Screens.Play if (!LoadedBeatmapSuccessfully) return; + Score = CreateScore(); + + // ensure the score is in a consistent state with the current player. + Score.ScoreInfo.Beatmap = Beatmap.Value.BeatmapInfo; + Score.ScoreInfo.Ruleset = rulesetInfo; + Score.ScoreInfo.Mods = Mods.Value.ToArray(); + PrepareReplay(); + ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo); + gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true); } - [CanBeNull] - private Score recordingScore; - /// /// Run any recording / playback setup for replays. /// protected virtual void PrepareReplay() { - DrawableRuleset.SetRecordTarget(recordingScore = new Score()); + DrawableRuleset.SetRecordTarget(Score); + } - ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(recordingScore.ScoreInfo); + protected virtual void PrepareScoreForResults() + { + // perform one final population to ensure everything is up-to-date. + ScoreProcessor.PopulateScore(Score.ScoreInfo); } [BackgroundDependencyLoader(true)] @@ -631,11 +640,11 @@ namespace osu.Game.Screens.Play prepareScoreForDisplayTask ??= Task.Run(async () => { - var score = CreateScore(); + PrepareScoreForResults(); try { - await PrepareScoreForResultsAsync(score).ConfigureAwait(false); + await PrepareScoreForResultsAsync(Score).ConfigureAwait(false); } catch (Exception ex) { @@ -644,14 +653,14 @@ namespace osu.Game.Screens.Play try { - await ImportScore(score).ConfigureAwait(false); + await ImportScore(Score).ConfigureAwait(false); } catch (Exception ex) { Logger.Error(ex, "Score import failed!"); } - return score.ScoreInfo; + return Score.ScoreInfo; }); if (skipStoryboardOutro) @@ -903,41 +912,19 @@ namespace osu.Game.Screens.Play } /// - /// Creates the player's . + /// Creates the player's . /// - /// The . - protected virtual Score CreateScore() - { - var score = new Score + /// The . + protected virtual Score CreateScore() => + new Score { - ScoreInfo = new ScoreInfo - { - Beatmap = Beatmap.Value.BeatmapInfo, - Ruleset = rulesetInfo, - Mods = Mods.Value.ToArray(), - } + ScoreInfo = new ScoreInfo { User = api.LocalUser.Value }, }; - if (DrawableRuleset.ReplayScore != null) - { - score.ScoreInfo.User = DrawableRuleset.ReplayScore.ScoreInfo?.User ?? new GuestUser(); - score.Replay = DrawableRuleset.ReplayScore.Replay; - } - else - { - score.ScoreInfo.User = api.LocalUser.Value; - score.Replay = new Replay { Frames = recordingScore?.Replay.Frames.ToList() ?? new List() }; - } - - ScoreProcessor.PopulateScore(score.ScoreInfo); - - return score; - } - /// - /// Imports the player's to the local database. + /// Imports the player's to the local database. /// - /// The to import. + /// The to import. /// The imported score. protected virtual async Task ImportScore(Score score) { @@ -968,9 +955,9 @@ namespace osu.Game.Screens.Play } /// - /// Prepare the for display at results. + /// Prepare the for display at results. /// - /// The to prepare. + /// 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; diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 91236ad607..e440e7d34e 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; @@ -16,8 +15,6 @@ namespace osu.Game.Screens.Play { public class ReplayPlayer : Player, IKeyBindingHandler { - protected Score Score { get; private set; } - private readonly Func, Score> createScore; // Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108) @@ -34,28 +31,15 @@ namespace osu.Game.Screens.Play this.createScore = createScore; } - [BackgroundDependencyLoader] - private void load() - { - if (!LoadedBeatmapSuccessfully) return; - - Score = createScore(GameplayBeatmap.PlayableBeatmap, Mods.Value); - } - protected override void PrepareReplay() { DrawableRuleset?.SetReplayScore(Score); + + // todo: move to base class along with Score? + ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo); } - protected override Score CreateScore() - { - var baseScore = base.CreateScore(); - - // Since the replay score doesn't contain statistics, we'll pass them through here. - Score.ScoreInfo.HitEvents = baseScore.ScoreInfo.HitEvents; - - return Score; - } + protected override Score CreateScore() => createScore(GameplayBeatmap.PlayableBeatmap, Mods.Value); // Don't re-import replay scores as they're already present in the database. protected override Task ImportScore(Score score) => Task.CompletedTask; diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index a8125dfded..67471dff90 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -18,6 +18,9 @@ namespace osu.Game.Screens.Play { private readonly Score score; + [Resolved] + private SpectatorClient spectatorClient { get; set; } + protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap public SpectatorPlayer(Score score) @@ -25,13 +28,10 @@ namespace osu.Game.Screens.Play this.score = score; } - protected override ResultsScreen CreateResults(ScoreInfo score) - { - return new SpectatorResultsScreen(score); - } + protected override Score CreateScore() => score; - [Resolved] - private SpectatorClient spectatorClient { get; set; } + protected override ResultsScreen CreateResults(ScoreInfo score) + => new SpectatorResultsScreen(score); [BackgroundDependencyLoader] private void load() From 5b436ee436a58d3a8d83898f5613fcdf18222268 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 2 Jun 2021 15:50:33 +0800 Subject: [PATCH 0348/1621] add beatmap with storyboard source --- osu.Game/Configuration/BackgroundSource.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/BackgroundSource.cs b/osu.Game/Configuration/BackgroundSource.cs index 5726e96eb1..18e0603860 100644 --- a/osu.Game/Configuration/BackgroundSource.cs +++ b/osu.Game/Configuration/BackgroundSource.cs @@ -1,11 +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.ComponentModel; + namespace osu.Game.Configuration { public enum BackgroundSource { Skin, - Beatmap + Beatmap, + + [Description("Beatmap (with storyboard / video)")] + BeatmapWithStoryboard, } } From dec18ef826defe08cce89cc5b92b01b30c4fef81 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 2 Jun 2021 15:50:58 +0800 Subject: [PATCH 0349/1621] implement BeatmapBackgroundWithStoryboard --- .../BeatmapBackgroundWithStoryboard.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs new file mode 100644 index 0000000000..20bf15af22 --- /dev/null +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Storyboards.Drawables; + +namespace osu.Game.Graphics.Backgrounds +{ + public class BeatmapBackgroundWithStoryboard : BeatmapBackground + { + private DrawableStoryboard storyboard; + + public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1") + : base(beatmap, fallbackTextureName) + { + } + + [BackgroundDependencyLoader] + private void load() + { + var clock = new InterpolatingFramedClock(Beatmap.Track); + LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) + { + Alpha = 0, + Clock = clock, + }, + loaded => + { + AddInternal(storyboard = loaded); + storyboard.FadeIn(300, Easing.OutQuint); + + if (Beatmap.Storyboard.ReplacesBackground) + Sprite.FadeOut(300, Easing.OutQuint); + }); + } + } +} From dde64adcb5102d0ea358e9eae8d9106a4b33b316 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 2 Jun 2021 15:51:43 +0800 Subject: [PATCH 0350/1621] add new background type in BackgroundScreenDefault --- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index bd4577fd57..81968db320 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -110,6 +110,10 @@ namespace osu.Game.Screens.Backgrounds newBackground = new BeatmapBackground(beatmap.Value, backgroundName); break; + case BackgroundSource.BeatmapWithStoryboard: + newBackground = new BeatmapBackgroundWithStoryboard(beatmap.Value, backgroundName); + break; + default: newBackground = new SkinnedBackground(skin.Value, backgroundName); break; From e66f6e8f91cd6783544d2bb1ea501d3f297e39aa Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 2 Jun 2021 16:12:41 +0800 Subject: [PATCH 0351/1621] fix inspect code issues and cleanup code --- .../BeatmapBackgroundWithStoryboard.cs | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 20bf15af22..0afdc52e49 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -11,8 +11,6 @@ namespace osu.Game.Graphics.Backgrounds { public class BeatmapBackgroundWithStoryboard : BeatmapBackground { - private DrawableStoryboard storyboard; - public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1") : base(beatmap, fallbackTextureName) { @@ -21,20 +19,19 @@ namespace osu.Game.Graphics.Backgrounds [BackgroundDependencyLoader] private void load() { - var clock = new InterpolatingFramedClock(Beatmap.Track); LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) - { - Alpha = 0, - Clock = clock, - }, - loaded => - { - AddInternal(storyboard = loaded); - storyboard.FadeIn(300, Easing.OutQuint); + { + Alpha = 0, + Clock = new InterpolatingFramedClock(Beatmap.Track), + }, + loaded => + { + AddInternal(loaded); + loaded.FadeIn(300, Easing.OutQuint); - if (Beatmap.Storyboard.ReplacesBackground) - Sprite.FadeOut(300, Easing.OutQuint); - }); + if (Beatmap.Storyboard.ReplacesBackground) + Sprite.FadeOut(300, Easing.OutQuint); + }); } } } From 3c3ef1363209db1ac261f37f35f5fba7f5a041ba Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 2 Jun 2021 16:28:22 +0800 Subject: [PATCH 0352/1621] remove fade --- .../Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 0afdc52e49..21c536ee30 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -21,14 +21,11 @@ namespace osu.Game.Graphics.Backgrounds { LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) { - Alpha = 0, Clock = new InterpolatingFramedClock(Beatmap.Track), }, loaded => { AddInternal(loaded); - loaded.FadeIn(300, Easing.OutQuint); - if (Beatmap.Storyboard.ReplacesBackground) Sprite.FadeOut(300, Easing.OutQuint); }); From e0eb0adb0af1aa7b30b875fa7aa8e5df756af96d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Jun 2021 18:32:14 +0900 Subject: [PATCH 0353/1621] Remove unnecessary bind in `ReplayPlayer` --- osu.Game/Screens/Play/ReplayPlayer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index e440e7d34e..f70c05c2ff 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -34,9 +34,6 @@ namespace osu.Game.Screens.Play protected override void PrepareReplay() { DrawableRuleset?.SetReplayScore(Score); - - // todo: move to base class along with Score? - ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo); } protected override Score CreateScore() => createScore(GameplayBeatmap.PlayableBeatmap, Mods.Value); From cde8de154d10e5470dac1f974938e861d2b8db72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Jun 2021 19:11:09 +0900 Subject: [PATCH 0354/1621] Remove unused test property for now --- osu.Game/Tests/Visual/TestReplayPlayer.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game/Tests/Visual/TestReplayPlayer.cs b/osu.Game/Tests/Visual/TestReplayPlayer.cs index ac47d186eb..da302d018d 100644 --- a/osu.Game/Tests/Visual/TestReplayPlayer.cs +++ b/osu.Game/Tests/Visual/TestReplayPlayer.cs @@ -3,9 +3,7 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -38,8 +36,6 @@ namespace osu.Game.Tests.Visual public new bool PauseCooldownActive => base.PauseCooldownActive; - public readonly List Results = new List(); - /// /// Instantiate a replay player that renders an autoplay mod. /// @@ -65,11 +61,5 @@ namespace osu.Game.Tests.Visual { PauseOnFocusLost = pauseOnFocusLost; } - - [BackgroundDependencyLoader] - private void load() - { - ScoreProcessor.NewJudgement += r => Results.Add(r); - } } } From 277545bb0652c8e2e56c474293200b5b2c1755da Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 2 Jun 2021 20:27:12 +0800 Subject: [PATCH 0355/1621] refactor BeatmapBackgroundWithStoryboard to reduce overhead This avoids loading the sprite if its not needed and instead of hiding it, it is removed when the storyboard replaces the background or there is a video. This also only initializes DrawableStoryboard if there are any elements in any layer. --- .../Graphics/Backgrounds/BeatmapBackground.cs | 11 +++++-- .../BeatmapBackgroundWithStoryboard.cs | 29 ++++++++++--------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs index 058d2ed0f9..2a4c5e194b 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs @@ -13,16 +13,21 @@ namespace osu.Game.Graphics.Backgrounds private readonly string fallbackTextureName; + [Resolved] + private LargeTextureStore textures { get; set; } + public BeatmapBackground(WorkingBeatmap beatmap, string fallbackTextureName = @"Backgrounds/bg1") { Beatmap = beatmap; this.fallbackTextureName = fallbackTextureName; } - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) + protected override void LoadComplete() { - Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName); + base.LoadComplete(); + Initialize(); } + + protected virtual void Initialize() => Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName); } } diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 21c536ee30..1925cb927e 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -1,8 +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 osu.Framework.Allocation; -using osu.Framework.Graphics; +using System.Linq; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Storyboards.Drawables; @@ -16,19 +15,21 @@ namespace osu.Game.Graphics.Backgrounds { } - [BackgroundDependencyLoader] - private void load() + protected override void Initialize() { - LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) - { - Clock = new InterpolatingFramedClock(Beatmap.Track), - }, - loaded => - { - AddInternal(loaded); - if (Beatmap.Storyboard.ReplacesBackground) - Sprite.FadeOut(300, Easing.OutQuint); - }); + if (Beatmap.Storyboard.HasDrawable) + { + LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }, AddInternal); + } + + if (Beatmap.Storyboard.ReplacesBackground || Beatmap.Storyboard.Layers.First(l => l.Name == "Video").Elements.Any()) + { + Sprite.Expire(); + } + else + { + base.Initialize(); + } } } } From d6656047e3e46a16abf8c2647237acb9e3b97e22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Jun 2021 01:58:50 +0900 Subject: [PATCH 0356/1621] Fix beatmap statistics with value of zero not displaying correctly at song select Closes #13307. --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index b0084735b1..53e30fd9ca 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -186,11 +186,11 @@ namespace osu.Game.Screens.Select.Details set => name.Text = value; } - private (float baseValue, float? adjustedValue) value; + private (float baseValue, float? adjustedValue)? value; public (float baseValue, float? adjustedValue) Value { - get => value; + get => value ?? (0, null); set { if (value == this.value) From a62dd7cca089b65990f273d185bd561699552a2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Jun 2021 12:33:16 +0900 Subject: [PATCH 0357/1621] Revert "refactor BeatmapBackgroundWithStoryboard to reduce overhead" This reverts commit 277545bb0652c8e2e56c474293200b5b2c1755da. --- .../Graphics/Backgrounds/BeatmapBackground.cs | 11 ++----- .../BeatmapBackgroundWithStoryboard.cs | 29 +++++++++---------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs index 2a4c5e194b..058d2ed0f9 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs @@ -13,21 +13,16 @@ namespace osu.Game.Graphics.Backgrounds private readonly string fallbackTextureName; - [Resolved] - private LargeTextureStore textures { get; set; } - public BeatmapBackground(WorkingBeatmap beatmap, string fallbackTextureName = @"Backgrounds/bg1") { Beatmap = beatmap; this.fallbackTextureName = fallbackTextureName; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) { - base.LoadComplete(); - Initialize(); + Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName); } - - protected virtual void Initialize() => Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName); } } diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 1925cb927e..21c536ee30 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -1,7 +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.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Storyboards.Drawables; @@ -15,21 +16,19 @@ namespace osu.Game.Graphics.Backgrounds { } - protected override void Initialize() + [BackgroundDependencyLoader] + private void load() { - if (Beatmap.Storyboard.HasDrawable) - { - LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }, AddInternal); - } - - if (Beatmap.Storyboard.ReplacesBackground || Beatmap.Storyboard.Layers.First(l => l.Name == "Video").Elements.Any()) - { - Sprite.Expire(); - } - else - { - base.Initialize(); - } + LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) + { + Clock = new InterpolatingFramedClock(Beatmap.Track), + }, + loaded => + { + AddInternal(loaded); + if (Beatmap.Storyboard.ReplacesBackground) + Sprite.FadeOut(300, Easing.OutQuint); + }); } } } From d00fb2118840e324cae17f52b481d81e0a7b9012 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 3 Jun 2021 13:24:21 +0800 Subject: [PATCH 0358/1621] prevent scaling container from creating a storyboard background --- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 ++ osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 2488fd14d0..d2b1e5e523 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -172,6 +172,8 @@ namespace osu.Game.Graphics.Containers private class ScalingBackgroundScreen : BackgroundScreenDefault { + protected override bool AllowStoryboardBackground => false; + public override void OnEntering(IScreen last) { this.FadeInFromZero(4000, Easing.OutQuint); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 81968db320..b02e7ddb0d 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -31,6 +31,8 @@ namespace osu.Game.Screens.Backgrounds [Resolved] private IBindable beatmap { get; set; } + protected virtual bool AllowStoryboardBackground => true; + public BackgroundScreenDefault(bool animateOnEnter = true) : base(animateOnEnter) { @@ -111,7 +113,9 @@ namespace osu.Game.Screens.Backgrounds break; case BackgroundSource.BeatmapWithStoryboard: - newBackground = new BeatmapBackgroundWithStoryboard(beatmap.Value, backgroundName); + newBackground = AllowStoryboardBackground + ? new BeatmapBackgroundWithStoryboard(beatmap.Value, backgroundName) + : new BeatmapBackground(beatmap.Value, backgroundName); break; default: From 62b07fb9ceadace33eb2801e0ff7461089d4ebea Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 3 Jun 2021 13:27:00 +0800 Subject: [PATCH 0359/1621] apply suggestions - Replace the sprite with a solid black box when a storyboard requests it. - Create a new storyboard instance and exclude the fail layer as well as strip all samples from it - Do not attempt in creating the storyboard when it isn't needed --- .../BeatmapBackgroundWithStoryboard.cs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 21c536ee30..1936482c90 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -1,10 +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.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; namespace osu.Game.Graphics.Backgrounds @@ -19,16 +22,23 @@ namespace osu.Game.Graphics.Backgrounds [BackgroundDependencyLoader] private void load() { - LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) - { - Clock = new InterpolatingFramedClock(Beatmap.Track), - }, - loaded => - { - AddInternal(loaded); - if (Beatmap.Storyboard.ReplacesBackground) - Sprite.FadeOut(300, Easing.OutQuint); - }); + if (!Beatmap.Storyboard.HasDrawable) + return; + + var storyboard = new Storyboard { BeatmapInfo = Beatmap.BeatmapInfo }; + foreach (var layer in storyboard.Layers) + { + if (layer.Name != "Fail") + layer.Elements = Beatmap.Storyboard.GetLayer(layer.Name).Elements.Where(e => !(e is StoryboardSampleInfo)).ToList(); + } + + if (storyboard.ReplacesBackground) + { + Sprite.Texture = Texture.WhitePixel; + Sprite.Colour = Colour4.Black; + } + + LoadComponentAsync(new DrawableStoryboard(storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }, AddInternal); } } } From 66dd7b77055cfe297273148bf308372a025f17a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Jun 2021 14:37:38 +0900 Subject: [PATCH 0360/1621] Update test logic to allow gameplay to properly continue --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index dd05ce9b7e..0308d74aa4 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; @@ -95,11 +96,12 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); - AddStep("set nofail", () => Game.SelectedMods.Value = new[] { new OsuModNoFail() }); + AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModDoubleTime { SpeedChange = { Value = 2 } } }); AddStep("press enter", () => InputManager.Key(Key.Enter)); AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); - AddStep("seek to end", () => player.ChildrenOfType().First().Seek(beatmap().Track.Length)); + AddUntilStep("wait for track playing", () => beatmap().Track.IsRunning); + AddStep("seek to near end", () => player.ChildrenOfType().First().Seek(beatmap().Beatmap.HitObjects[^1].StartTime - 1000)); AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded); AddStep("attempt to retry", () => results.ChildrenOfType().First().Action()); AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player); From 94701b77cb261963a66106c0b4270a59de6dfc24 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Thu, 3 Jun 2021 15:44:28 +1000 Subject: [PATCH 0361/1621] Add TODO for variable clockrate support in catch difficulty calculator --- osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 7d61be7bb1..83db9216ed 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -39,6 +39,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills // In catch, rate adjustment mods do not only affect the timings of hitobjects, // but also the speed of the player's catcher, which has an impact on difficulty + // TODO: Support variable clockrates caused by mods such as ModTimeRamp + // (perhaps by using IApplicableToRate within the CatchDifficultyHitObject constructor to set a catcher speed for each object before processing) var rateAdjustMod = mods.FirstOrDefault(m => m is ModRateAdjust); catcherSpeedMultiplier = (rateAdjustMod as ModRateAdjust)?.SpeedChange.Value ?? 1; } From d7d0dde5d27c84cfb2e7380158d1c4e26b1cd40a Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 3 Jun 2021 13:56:14 +0800 Subject: [PATCH 0362/1621] use created storyboard to check for drawables instead --- .../Backgrounds/BeatmapBackgroundWithStoryboard.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 1936482c90..9695e93f5d 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -22,16 +22,17 @@ namespace osu.Game.Graphics.Backgrounds [BackgroundDependencyLoader] private void load() { - if (!Beatmap.Storyboard.HasDrawable) - return; - var storyboard = new Storyboard { BeatmapInfo = Beatmap.BeatmapInfo }; + foreach (var layer in storyboard.Layers) { if (layer.Name != "Fail") layer.Elements = Beatmap.Storyboard.GetLayer(layer.Name).Elements.Where(e => !(e is StoryboardSampleInfo)).ToList(); } + if (!storyboard.HasDrawable) + return; + if (storyboard.ReplacesBackground) { Sprite.Texture = Texture.WhitePixel; From a6cc37eb3b7a84de7159ea1e4f270f3f157419a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Jun 2021 14:56:21 +0900 Subject: [PATCH 0363/1621] Mark fields `readonly` --- osu.Game/Online/Chat/ChannelManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index e5e38e425f..f6804fdbf7 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -587,8 +587,8 @@ namespace osu.Game.Online.Chat /// public class ClosedChannel { - public ChannelType Type; - public long Id; + public readonly ChannelType Type; + public readonly long Id; public ClosedChannel(ChannelType type, long id) { From a91015302e5de78319fcddad63ad6888def53076 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Jun 2021 14:56:29 +0900 Subject: [PATCH 0364/1621] Replace second usage of new function --- osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index c314e10aff..c0de093425 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -81,9 +81,11 @@ namespace osu.Game.Overlays.Chat.Tabs RemoveItem(channel); if (SelectedTab == null) - SelectTab(selectorTab); + SelectChannelSelectorTab(); } + public void SelectChannelSelectorTab() => SelectTab(selectorTab); + protected override void SelectTab(TabItem tab) { if (tab is ChannelSelectorTabItem) @@ -96,11 +98,6 @@ namespace osu.Game.Overlays.Chat.Tabs selectorTab.Active.Value = false; } - public void SelectChannelSelectorTab() - { - SelectTab(selectorTab); - } - protected override TabFillFlowContainer CreateTabFlow() => new ChannelTabFillFlowContainer { Direction = FillDirection.Full, From fe2934db1daa6681bae7342782fc8b7635b4cec8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 31 May 2021 22:57:43 +0900 Subject: [PATCH 0365/1621] Factor out lifetime management logic of HitObjectContainer --- .../PooledDrawableWithLifetimeContainer.cs | 157 ++++++++++++++++++ osu.Game/Rulesets/UI/HitObjectContainer.cs | 94 ++--------- 2 files changed, 175 insertions(+), 76 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs new file mode 100644 index 0000000000..656e4a9dd6 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.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. + +#nullable enable + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; + +namespace osu.Game.Rulesets.Objects.Pooling +{ + /// + /// A container of s dynamically added/removed by model s. + /// When an entry became alive, a drawable corresponding to the entry is obtained (potentially pooled), and added to this container. + /// The drawable is removed when the entry became dead. + /// + /// The type of entries managed by this container. + /// The type of drawables corresponding to the entries. + public abstract class PooledDrawableWithLifetimeContainer : CompositeDrawable + where TEntry : LifetimeEntry + where TDrawable : Drawable + { + /// + /// All entries added to this container, including dead entries. + /// + /// + /// The enumeration order is undefined. + /// + public IEnumerable Entries => allEntries; + + /// + /// All alive entries and drawables corresponding to the entries. + /// + /// + /// The enumeration order is undefined. + /// + public IEnumerable<(TEntry Entry, TDrawable Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value)); + + /// + /// The amount of time prior to the current time within which entries should be considered alive. + /// + internal double PastLifetimeExtension { get; set; } + + /// + /// The amount of time after the current time within which entries should be considered alive. + /// + internal double FutureLifetimeExtension { get; set; } + + private readonly Dictionary aliveDrawableMap = new Dictionary(); + private readonly HashSet allEntries = new HashSet(); + + private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); + + protected PooledDrawableWithLifetimeContainer() + { + lifetimeManager.EntryBecameAlive += entryBecameAlive; + lifetimeManager.EntryBecameDead += entryBecameDead; + lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; + } + + /// + /// Add a to be managed by this container. + /// + /// + /// The aliveness of the entry is not updated until . + /// + public virtual void Add(TEntry entry) + { + allEntries.Add(entry); + lifetimeManager.AddEntry(entry); + } + + /// + /// Remove a from this container. + /// + /// + /// If the entry was alive, the corresponding drawable is removed. + /// + /// Whether the entry was in this container. + public virtual bool Remove(TEntry entry) + { + if (!lifetimeManager.RemoveEntry(entry)) return false; + + allEntries.Remove(entry); + return true; + } + + /// + /// Initialize new corresponding . + /// + /// The corresponding to the entry. + protected abstract TDrawable GetDrawable(TEntry entry); + + private void entryBecameAlive(LifetimeEntry lifetimeEntry) + { + var entry = (TEntry)lifetimeEntry; + Debug.Assert(!aliveDrawableMap.ContainsKey(entry)); + + TDrawable drawable = GetDrawable(entry); + aliveDrawableMap[entry] = drawable; + AddDrawable(entry, drawable); + } + + /// + /// Add a corresponding to to this container. + /// + /// + /// Invoked when the entry became alive and a is obtained by . + /// + protected virtual void AddDrawable(TEntry entry, TDrawable drawable) => AddInternal(drawable); + + private void entryBecameDead(LifetimeEntry lifetimeEntry) + { + var entry = (TEntry)lifetimeEntry; + Debug.Assert(aliveDrawableMap.ContainsKey(entry)); + + TDrawable drawable = aliveDrawableMap[entry]; + aliveDrawableMap.Remove(entry); + RemoveDrawable(entry, drawable); + } + + /// + /// Remove a corresponding to from this container. + /// + /// + /// Invoked when the entry became dead. + /// + protected virtual void RemoveDrawable(TEntry entry, TDrawable drawable) => RemoveInternal(drawable); + + private void entryCrossedBoundary(LifetimeEntry lifetimeEntry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) => + OnEntryCrossedBoundary((TEntry)lifetimeEntry, kind, direction); + + protected virtual void OnEntryCrossedBoundary(TEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) + { + } + + /// + /// Remove all s. + /// + public virtual void Clear() + { + foreach (var entry in Entries.ToArray()) + Remove(entry); + Debug.Assert(aliveDrawableMap.Count == 0); + } + + protected override bool CheckChildrenLife() + { + bool aliveChanged = base.CheckChildrenLife(); + aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension); + return aliveChanged; + } + } +} diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 83033b2dd5..ff7a368c0c 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -14,24 +14,15 @@ using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Pooling; namespace osu.Game.Rulesets.UI { - public class HitObjectContainer : CompositeDrawable, IHitObjectContainer + public class HitObjectContainer : PooledDrawableWithLifetimeContainer, IHitObjectContainer { - /// - /// All entries in this including dead entries. - /// - public IEnumerable Entries => allEntries; - - /// - /// All alive entries and s used by the entries. - /// - public IEnumerable<(HitObjectLifetimeEntry Entry, DrawableHitObject Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value)); - public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); - public IEnumerable AliveObjects => AliveInternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); + public IEnumerable AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime); /// /// Invoked when a is judged. @@ -59,34 +50,16 @@ namespace osu.Game.Rulesets.UI /// internal event Action HitObjectUsageFinished; - /// - /// The amount of time prior to the current time within which s should be considered alive. - /// - internal double PastLifetimeExtension { get; set; } - - /// - /// The amount of time after the current time within which s should be considered alive. - /// - internal double FutureLifetimeExtension { get; set; } - private readonly Dictionary startTimeMap = new Dictionary(); - private readonly Dictionary aliveDrawableMap = new Dictionary(); private readonly Dictionary nonPooledDrawableMap = new Dictionary(); - private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); - private readonly HashSet allEntries = new HashSet(); - [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } public HitObjectContainer() { RelativeSizeAxes = Axes.Both; - - lifetimeManager.EntryBecameAlive += entryBecameAlive; - lifetimeManager.EntryBecameDead += entryBecameDead; - lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; } protected override void LoadAsyncComplete() @@ -99,37 +72,29 @@ namespace osu.Game.Rulesets.UI #region Pooling support - public void Add(HitObjectLifetimeEntry entry) + public override bool Remove(HitObjectLifetimeEntry entry) { - allEntries.Add(entry); - lifetimeManager.AddEntry(entry); - } - - public bool Remove(HitObjectLifetimeEntry entry) - { - if (!lifetimeManager.RemoveEntry(entry)) return false; + if (!base.Remove(entry)) return false; // This logic is not in `Remove(DrawableHitObject)` because a non-pooled drawable may be removed by specifying its entry. if (nonPooledDrawableMap.Remove(entry, out var drawable)) removeDrawable(drawable); - allEntries.Remove(entry); return true; } - private void entryBecameAlive(LifetimeEntry lifetimeEntry) + protected sealed override DrawableHitObject GetDrawable(HitObjectLifetimeEntry entry) { - var entry = (HitObjectLifetimeEntry)lifetimeEntry; - Debug.Assert(!aliveDrawableMap.ContainsKey(entry)); + if (nonPooledDrawableMap.TryGetValue(entry, out var drawable)) + return drawable; - bool isPooled = !nonPooledDrawableMap.TryGetValue(entry, out var drawable); - drawable ??= pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null); - if (drawable == null) - throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}."); + return pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null) ?? + throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}."); + } - aliveDrawableMap[entry] = drawable; - - if (isPooled) + protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) + { + if (!nonPooledDrawableMap.ContainsKey(entry)) { addDrawable(drawable); HitObjectUsageBegan?.Invoke(entry.HitObject); @@ -138,18 +103,11 @@ namespace osu.Game.Rulesets.UI OnAdd(drawable); } - private void entryBecameDead(LifetimeEntry lifetimeEntry) + protected override void RemoveDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) { - var entry = (HitObjectLifetimeEntry)lifetimeEntry; - Debug.Assert(aliveDrawableMap.ContainsKey(entry)); - - var drawable = aliveDrawableMap[entry]; - bool isPooled = !nonPooledDrawableMap.ContainsKey(entry); - drawable.OnKilled(); - aliveDrawableMap.Remove(entry); - if (isPooled) + if (!nonPooledDrawableMap.ContainsKey(entry)) { removeDrawable(drawable); HitObjectUsageFinished?.Invoke(entry.HitObject); @@ -201,9 +159,9 @@ namespace osu.Game.Rulesets.UI public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject); - private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) + protected override void OnEntryCrossedBoundary(HitObjectLifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) { - if (nonPooledDrawableMap.TryGetValue((HitObjectLifetimeEntry)entry, out var drawable)) + if (nonPooledDrawableMap.TryGetValue(entry, out var drawable)) OnChildLifetimeBoundaryCrossed(new LifetimeBoundaryCrossedEvent(drawable, kind, direction)); } @@ -228,22 +186,6 @@ namespace osu.Game.Rulesets.UI { } - public virtual void Clear() - { - lifetimeManager.ClearEntries(); - foreach (var drawable in nonPooledDrawableMap.Values) - removeDrawable(drawable); - nonPooledDrawableMap.Clear(); - Debug.Assert(InternalChildren.Count == 0 && startTimeMap.Count == 0 && aliveDrawableMap.Count == 0, "All hit objects should have been removed"); - } - - protected override bool CheckChildrenLife() - { - bool aliveChanged = base.CheckChildrenLife(); - aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension); - return aliveChanged; - } - private void onNewResult(DrawableHitObject d, JudgementResult r) => NewResult?.Invoke(d, r); private void onRevertResult(DrawableHitObject d, JudgementResult r) => RevertResult?.Invoke(d, r); From 2c9e5b6c7e7b965ac08325b76345522777442b49 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 31 May 2021 23:00:12 +0900 Subject: [PATCH 0366/1621] Replace `EntryCrossedBoundary` with more useful `RemoveRewoundEntry` property It can be used for dynamically added entries. --- .../UI/DrumRollHitContainer.cs | 17 +++++------------ .../PooledDrawableWithLifetimeContainer.cs | 13 +++++++++---- osu.Game/Rulesets/UI/HitObjectContainer.cs | 12 ------------ 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs index 9bfb6aa839..263454c78a 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.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 osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; @@ -11,6 +8,11 @@ namespace osu.Game.Rulesets.Taiko.UI { internal class DrumRollHitContainer : ScrollingHitObjectContainer { + // TODO: this usage is buggy. + // Because `LifetimeStart` is set based on scrolling, lifetime is not same as the time when the object is created. + // If the `Update` override is removed, it breaks in an obscure way. + protected override bool RemoveRewoundEntry => true; + protected override void Update() { base.Update(); @@ -23,14 +25,5 @@ namespace osu.Game.Rulesets.Taiko.UI Remove(flyingHit); } } - - protected override void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e) - { - base.OnChildLifetimeBoundaryCrossed(e); - - // ensure all old hits are removed on becoming alive (may miss being in the AliveInternalChildren list above). - if (e.Kind == LifetimeBoundaryKind.Start && e.Direction == LifetimeBoundaryCrossingDirection.Backward) - Remove((DrawableHitObject)e.Child); - } } } diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs index 656e4a9dd6..5fe9d889a1 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs @@ -39,6 +39,12 @@ namespace osu.Game.Rulesets.Objects.Pooling /// public IEnumerable<(TEntry Entry, TDrawable Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value)); + /// + /// Whether to remove an entry when clock goes backward and crossed its . + /// Used when entries are dynamically added at its to prevent duplicated entries. + /// + protected virtual bool RemoveRewoundEntry => false; + /// /// The amount of time prior to the current time within which entries should be considered alive. /// @@ -130,11 +136,10 @@ namespace osu.Game.Rulesets.Objects.Pooling /// protected virtual void RemoveDrawable(TEntry entry, TDrawable drawable) => RemoveInternal(drawable); - private void entryCrossedBoundary(LifetimeEntry lifetimeEntry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) => - OnEntryCrossedBoundary((TEntry)lifetimeEntry, kind, direction); - - protected virtual void OnEntryCrossedBoundary(TEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) + private void entryCrossedBoundary(LifetimeEntry lifetimeEntry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) { + if (RemoveRewoundEntry && kind == LifetimeBoundaryKind.Start && direction == LifetimeBoundaryCrossingDirection.Backward) + Remove((TEntry)lifetimeEntry); } /// diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index ff7a368c0c..917aa54673 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -9,8 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -159,16 +157,6 @@ namespace osu.Game.Rulesets.UI public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject); - protected override void OnEntryCrossedBoundary(HitObjectLifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) - { - if (nonPooledDrawableMap.TryGetValue(entry, out var drawable)) - OnChildLifetimeBoundaryCrossed(new LifetimeBoundaryCrossedEvent(drawable, kind, direction)); - } - - protected virtual void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e) - { - } - #endregion /// From 0ce7baa3f3e80b640cf6c8d07400bc3465509368 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 31 May 2021 23:01:54 +0900 Subject: [PATCH 0367/1621] Make `HitObjectContainer.Clear` non-virtual It just call `Remove` for all entries. --- .../Objects/Pooling/PooledDrawableWithLifetimeContainer.cs | 3 ++- .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 7 ------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs index 5fe9d889a1..d35933dba8 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs @@ -145,10 +145,11 @@ namespace osu.Game.Rulesets.Objects.Pooling /// /// Remove all s. /// - public virtual void Clear() + public void Clear() { foreach (var entry in Entries.ToArray()) Remove(entry); + Debug.Assert(aliveDrawableMap.Count == 0); } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index cde4182f2d..b60aabe0e6 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -45,13 +45,6 @@ namespace osu.Game.Rulesets.UI.Scrolling timeRange.ValueChanged += _ => layoutCache.Invalidate(); } - public override void Clear() - { - base.Clear(); - - layoutComputed.Clear(); - } - /// /// Given a position in screen space, return the time within this column. /// From b321b20e9d5087bd5b0a0a208d616d751dc9abc6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 31 May 2021 23:07:32 +0900 Subject: [PATCH 0368/1621] Remove `OnAdd`/`OnRemove` of `HitObjectContainer` Instead, override `AddDrawable`/`RemoveDrawable`. --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 35 ++++--------------- .../Scrolling/ScrollingHitObjectContainer.cs | 20 +++++++---- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 917aa54673..fee77af0ba 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -92,26 +91,19 @@ namespace osu.Game.Rulesets.UI protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) { - if (!nonPooledDrawableMap.ContainsKey(entry)) - { - addDrawable(drawable); - HitObjectUsageBegan?.Invoke(entry.HitObject); - } + if (nonPooledDrawableMap.ContainsKey(entry)) return; - OnAdd(drawable); + addDrawable(drawable); + HitObjectUsageBegan?.Invoke(entry.HitObject); } protected override void RemoveDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) { drawable.OnKilled(); + if (nonPooledDrawableMap.ContainsKey(entry)) return; - if (!nonPooledDrawableMap.ContainsKey(entry)) - { - removeDrawable(drawable); - HitObjectUsageFinished?.Invoke(entry.HitObject); - } - - OnRemove(drawable); + removeDrawable(drawable); + HitObjectUsageFinished?.Invoke(entry.HitObject); } private void addDrawable(DrawableHitObject drawable) @@ -159,21 +151,6 @@ namespace osu.Game.Rulesets.UI #endregion - /// - /// Invoked after a is added to this container. - /// - protected virtual void OnAdd(DrawableHitObject drawableHitObject) - { - Debug.Assert(drawableHitObject.LoadState >= LoadState.Ready); - } - - /// - /// Invoked after a is removed from this container. - /// - protected virtual void OnRemove(DrawableHitObject drawableHitObject) - { - } - private void onNewResult(DrawableHitObject d, JudgementResult r) => NewResult?.Invoke(d, r); private void onRevertResult(DrawableHitObject d, JudgementResult r) => RevertResult?.Invoke(d, r); diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index b60aabe0e6..f478e37e3e 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Layout; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -140,17 +142,20 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - protected override void OnAdd(DrawableHitObject drawableHitObject) + protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) { - invalidateHitObject(drawableHitObject); - drawableHitObject.DefaultsApplied += invalidateHitObject; + base.AddDrawable(entry, drawable); + + invalidateHitObject(drawable); + drawable.DefaultsApplied += invalidateHitObject; } - protected override void OnRemove(DrawableHitObject drawableHitObject) + protected override void RemoveDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) { - layoutComputed.Remove(drawableHitObject); + base.RemoveDrawable(entry, drawable); - drawableHitObject.DefaultsApplied -= invalidateHitObject; + drawable.DefaultsApplied -= invalidateHitObject; + layoutComputed.Remove(drawable); } private void invalidateHitObject(DrawableHitObject hitObject) @@ -199,6 +204,9 @@ namespace osu.Game.Rulesets.UI.Scrolling private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject) { + // Origin position may be relative to the parent size + Debug.Assert(hitObject.Parent != null); + float originAdjustment = 0.0f; // calculate the dimension of the part of the hitobject that should already be visible From e74fe68c9656947476ed88e6ef859b7a75de8c60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Jun 2021 15:04:07 +0900 Subject: [PATCH 0369/1621] Use new platform actions instead of hardcoded keys --- osu.Game/Overlays/ChatOverlay.cs | 50 ++++++++++++++++---------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 84653226cc..0aa6108815 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -24,13 +24,15 @@ using osu.Game.Overlays.Chat.Tabs; using osuTK.Input; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Framework.Localisation; using osu.Game.Localisation; using osu.Game.Online; namespace osu.Game.Overlays { - public class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent + public class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, IKeyBindingHandler { public string IconTexture => "Icons/Hexacons/messaging"; public LocalisableString Title => ChatStrings.HeaderTitle; @@ -367,33 +369,31 @@ namespace osu.Game.Overlays } } - if (e.ControlPressed) - { - if (e.ShiftPressed) - { - switch (e.Key) - { - case Key.T: - channelManager.JoinLastClosedChannel(); - return true; - } - } - else - { - switch (e.Key) - { - case Key.W: - channelManager.LeaveChannel(channelManager.CurrentChannel.Value); - return true; + return base.OnKeyDown(e); + } - case Key.T: - ChannelTabControl.SelectChannelSelectorTab(); - return true; - } - } + public bool OnPressed(PlatformAction action) + { + switch (action.ActionType) + { + case PlatformActionType.TabNew: + ChannelTabControl.SelectChannelSelectorTab(); + return true; + + case PlatformActionType.TabRestore: + channelManager.JoinLastClosedChannel(); + return true; + + case PlatformActionType.DocumentClose: + channelManager.LeaveChannel(channelManager.CurrentChannel.Value); + return true; } - return base.OnKeyDown(e); + return false; + } + + public void OnReleased(PlatformAction action) + { } public override bool AcceptsFocus => true; From f51413ead9a0c1afba047526ff9fae183ff1c9db Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Thu, 3 Jun 2021 16:09:37 +1000 Subject: [PATCH 0370/1621] Refactor to pass clockrate in constructor rather than deriving from mods --- .../EmptyFreeformDifficultyCalculator.cs | 2 +- .../PippidonDifficultyCalculator.cs | 2 +- .../EmptyScrollingDifficultyCalculator.cs | 2 +- .../PippidonDifficultyCalculator.cs | 2 +- .../Difficulty/CatchDifficultyCalculator.cs | 4 ++-- osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs | 8 +++----- .../Difficulty/ManiaDifficultyCalculator.cs | 2 +- .../Difficulty/OsuDifficultyCalculator.cs | 2 +- .../Difficulty/TaikoDifficultyCalculator.cs | 2 +- .../NonVisual/DifficultyAdjustmentModCombinationsTest.cs | 2 +- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 5 +++-- 11 files changed, 16 insertions(+), 17 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs index 59a68245a6..a80f1178b6 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs @@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.EmptyFreeform protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty(); - protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0]; + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[0]; } } diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs index f6340f6c25..290148d14b 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs @@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.Pippidon protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty(); - protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0]; + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[0]; } } diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs index 7f29c4e712..f557a4c754 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs @@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.EmptyScrolling protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty(); - protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0]; + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[0]; } } diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs index f6340f6c25..290148d14b 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs @@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.Pippidon protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty(); - protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0]; + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[0]; } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index f5cce47186..9feaa55051 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty } } - protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) { halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f; @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty return new Skill[] { - new Movement(mods, halfCatcherWidth), + new Movement(mods, halfCatcherWidth, clockRate), }; } diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 849af75228..4372ed938c 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; @@ -34,17 +33,16 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills /// private readonly double catcherSpeedMultiplier; - public Movement(Mod[] mods, float halfCatcherWidth) + public Movement(Mod[] mods, float halfCatcherWidth, double clockRate) : base(mods) { HalfCatcherWidth = halfCatcherWidth; - // In catch, rate adjustment mods do not only affect the timings of hitobjects, + // In catch, clockrate adjustments do not only affect the timings of hitobjects, // but also the speed of the player's catcher, which has an impact on difficulty // TODO: Support variable clockrates caused by mods such as ModTimeRamp // (perhaps by using IApplicableToRate within the CatchDifficultyHitObject constructor to set a catcher speed for each object before processing) - var rateAdjustMod = mods.FirstOrDefault(m => m is ModRateAdjust); - catcherSpeedMultiplier = (rateAdjustMod as ModRateAdjust)?.SpeedChange.Value ?? 1; + catcherSpeedMultiplier = clockRate; } protected override double StrainValueOf(DifficultyHitObject current) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 8c0b9ed8b7..a7a6677b68 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty // Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required. protected override IEnumerable SortObjects(IEnumerable input) => input; - protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[] + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[] { new Strain(mods, ((ManiaBeatmap)beatmap).TotalColumns) }; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 75d6786d95..e47f82fb39 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty } } - protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[] + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[] { new Aim(mods), new Speed(mods) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 6b3e31c5d5..18d06c069f 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { } - protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[] + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[] { new Colour(mods), new Rhythm(mods), diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 16c1004f37..e458e66ab7 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -217,7 +217,7 @@ namespace osu.Game.Tests.NonVisual throw new NotImplementedException(); } - protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) { throw new NotImplementedException(); } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 5780fe39fa..3cc69bd85b 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Difficulty private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { - var skills = CreateSkills(beatmap, mods); + var skills = CreateSkills(beatmap, mods, clockRate); if (!beatmap.HitObjects.Any()) return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); @@ -180,7 +180,8 @@ namespace osu.Game.Rulesets.Difficulty /// /// The whose difficulty will be calculated. /// Mods to calculate difficulty with. + /// Clockrate to calculate difficulty with. /// The s. - protected abstract Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods); + protected abstract Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate); } } From b917d6d80c2edf191a6548762aca01e288e2562a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Jun 2021 15:34:06 +0900 Subject: [PATCH 0371/1621] Update tests to check for correct platform action keys --- .../Visual/Online/TestSceneChatOverlay.cs | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 8dbccde3d6..c40b60c35d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -9,6 +9,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -38,6 +40,9 @@ namespace osu.Game.Tests.Visual.Online private Channel channel2 => channels[1]; private Channel channel3 => channels[2]; + [Resolved] + private GameHost host { get; set; } + public TestSceneChatOverlay() { channels = Enumerable.Range(1, 10) @@ -231,7 +236,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestCtrlWShortcut() + public void TestCloseTabShortcut() { AddStep("Join 2 channels", () => { @@ -241,7 +246,7 @@ namespace osu.Game.Tests.Visual.Online // Want to close channel 2 AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); - AddStep("Press Ctrl + W", pressControlW); + AddStep("Press keys", pressCloseDocumentKeys); // Channel 2 should be closed AddAssert("Channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); @@ -250,13 +255,13 @@ namespace osu.Game.Tests.Visual.Online // Want to close channel 1 AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - AddStep("Press Ctrl + W", pressControlW); + AddStep("Press keys", pressCloseDocumentKeys); // Channel 1 and channel 2 should be closed AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } [Test] - public void TestCtrlTShortcut() + public void TestNewTabShortcut() { AddStep("Join 2 channels", () => { @@ -265,14 +270,14 @@ namespace osu.Game.Tests.Visual.Online }); // Want to join another channel - AddStep("Press Ctrl + T", pressControlT); + AddStep("Press keys", pressNewTabKeys); // Selector should be visible AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); } [Test] - public void TestCtrlShiftTShortcut() + public void TestRestoreTabShortcut() { AddStep("Join 3 channels", () => { @@ -282,7 +287,7 @@ namespace osu.Game.Tests.Visual.Online }); // Should do nothing - AddStep("Press Ctrl + Shift + T", pressControlShiftT); + AddStep("Press keys", pressRestoreTabKeys); AddAssert("All channels still open", () => channelManager.JoinedChannels.Count == 3); // Close channel 1 @@ -292,7 +297,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Other channels still open", () => channelManager.JoinedChannels.Count == 2); // Reopen channel 1 - AddStep("Press Ctrl + Shift + T", pressControlShiftT); + AddStep("Press keys", pressRestoreTabKeys); AddAssert("All channels now open", () => channelManager.JoinedChannels.Count == 3); AddAssert("Current channel is channel 1", () => currentChannel == channel1); @@ -305,13 +310,13 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Current channel is channel 3", () => currentChannel == channel3); // Should first re-open channel 2 - AddStep("Press Ctrl + Shift + T", pressControlShiftT); + AddStep("Press keys", pressRestoreTabKeys); AddAssert("Channel 1 still closed", () => !channelManager.JoinedChannels.Contains(channel1)); AddAssert("Channel 2 now open", () => channelManager.JoinedChannels.Contains(channel2)); AddAssert("Current channel is channel 2", () => currentChannel == channel2); // Should then re-open channel 1 - AddStep("Press Ctrl + Shift + T", pressControlShiftT); + AddStep("Press keys", pressRestoreTabKeys); AddAssert("All channels now open", () => channelManager.JoinedChannels.Count == 3); AddAssert("Current channel is channel 1", () => currentChannel == channel1); } @@ -324,27 +329,21 @@ namespace osu.Game.Tests.Visual.Online InputManager.ReleaseKey(Key.AltLeft); } - private void pressControlW() - { - InputManager.PressKey(Key.ControlLeft); - InputManager.Key(Key.W); - InputManager.ReleaseKey(Key.ControlLeft); - } + private void pressCloseDocumentKeys() => pressKeysFor(PlatformActionType.DocumentClose); - private void pressControlT() - { - InputManager.PressKey(Key.ControlLeft); - InputManager.Key(Key.T); - InputManager.ReleaseKey(Key.ControlLeft); - } + private void pressNewTabKeys() => pressKeysFor(PlatformActionType.TabNew); - private void pressControlShiftT() + private void pressRestoreTabKeys() => pressKeysFor(PlatformActionType.TabRestore); + + private void pressKeysFor(PlatformActionType type) { - InputManager.PressKey(Key.ControlLeft); - InputManager.PressKey(Key.ShiftLeft); - InputManager.Key(Key.T); - InputManager.ReleaseKey(Key.ShiftLeft); - InputManager.ReleaseKey(Key.ControlLeft); + var binding = host.PlatformKeyBindings.First(b => ((PlatformAction)b.Action).ActionType == type); + + foreach (var k in binding.KeyCombination.Keys) + InputManager.PressKey((Key)k); + + foreach (var k in binding.KeyCombination.Keys) + InputManager.ReleaseKey((Key)k); } private void clickDrawable(Drawable d) From be91203c92ba7004f0f03b32878b3a4182092584 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Jun 2021 15:35:12 +0900 Subject: [PATCH 0372/1621] Add nested `PlatformActionContainer` to allow testing of platform actions in visual tests --- osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index 01dd7a25c8..c7edc0174a 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -4,6 +4,7 @@ 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; @@ -48,7 +49,7 @@ namespace osu.Game.Tests.Visual InputManager = new ManualInputManager { UseParentInput = true, - Child = mainContent + Child = new PlatformActionContainer().WithChild(mainContent) }, new Container { From 420df124b5fba03eaf59196b5a2d70f2c973f4c8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Jun 2021 17:27:21 +0900 Subject: [PATCH 0373/1621] Add framestable-bypassing seek for spectator --- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 1 + osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 ++- osu.Game/Screens/Play/Player.cs | 18 ++++++++++++ osu.Game/Screens/Play/SpectatorPlayer.cs | 28 ++++++++++--------- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 2c5443fe08..ab13095af4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -177,6 +177,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override Container Overlays { get; } public override Container FrameStableComponents { get; } public override IFrameStableClock FrameStableClock { get; } + internal override bool FrameStablePlayback { get; set; } public override IReadOnlyList Mods { get; } public override double GameplayStartTime { get; } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index a2dade2627..0cd5804fd0 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.UI /// /// Whether to enable frame-stable playback. /// - internal bool FrameStablePlayback + internal override bool FrameStablePlayback { get => frameStablePlayback; set @@ -432,6 +432,8 @@ namespace osu.Game.Rulesets.UI /// public abstract IFrameStableClock FrameStableClock { get; } + internal abstract bool FrameStablePlayback { get; set; } + /// /// The mods which are to be applied. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2258d509b7..f8f9103c89 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -576,6 +576,24 @@ namespace osu.Game.Screens.Play /// The destination time to seek to. public void Seek(double time) => GameplayClockContainer.Seek(time); + /// + /// Seeks to a specific time in gameplay, bypassing frame stability. + /// + /// + /// Intermediate hitobject judgements may not be applied or reverted correctly during this seek. + /// + /// The destination time to seek to. + public void NonFrameStableSeek(double time) + { + bool wasFrameStable = DrawableRuleset.FrameStablePlayback; + DrawableRuleset.FrameStablePlayback = false; + + Seek(time); + + // Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek. + ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable); + } + /// /// Restart gameplay via a parent . /// This can be called from a child screen in order to trigger the restart process. diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index c5e26bdef6..605702c8e2 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.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 System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Spectator; @@ -50,6 +48,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); spectatorClient.OnNewFrames += userSentFrames; + seekToGameplay(); } private void userSentFrames(int userId, FrameDataBundle bundle) @@ -73,6 +72,20 @@ namespace osu.Game.Screens.Play score.Replay.Frames.Add(convertedFrame); } + + seekToGameplay(); + } + + private bool seekedToGameplay; + + private void seekToGameplay() + { + if (seekedToGameplay || score.Replay.Frames.Count == 0) + return; + + NonFrameStableSeek(score.Replay.Frames[0].Time); + + seekedToGameplay = true; } protected override ResultsScreen CreateResults(ScoreInfo score) @@ -85,17 +98,6 @@ namespace osu.Game.Screens.Play DrawableRuleset?.SetReplayScore(score); } - protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) - { - // if we already have frames, start gameplay at the point in time they exist, should they be too far into the beatmap. - double? firstFrameTime = score.Replay.Frames.FirstOrDefault()?.Time; - - if (firstFrameTime == null || firstFrameTime <= gameplayStart + 5000) - return base.CreateGameplayClockContainer(beatmap, gameplayStart); - - return new MasterGameplayClockContainer(beatmap, firstFrameTime.Value, true); - } - public override bool OnExiting(IScreen next) { spectatorClient.OnUserBeganPlaying -= userBeganPlaying; From 3bc81fbb068b478abb16ae49670056a818079844 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Jun 2021 17:27:24 +0900 Subject: [PATCH 0374/1621] Fix spectator tests --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 9606d8c828..6eeb3596a8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -76,9 +76,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("screen hasn't changed", () => Stack.CurrentScreen is SoloSpectator); start(); - sendFrames(); - waitForPlayer(); + + sendFrames(); AddAssert("ensure frames arrived", () => replayHandler.HasFrames); AddUntilStep("wait for frame starvation", () => replayHandler.WaitingForFrame); @@ -116,12 +116,11 @@ namespace osu.Game.Tests.Visual.Gameplay start(); loadSpectatingScreen(); + waitForPlayer(); AddStep("advance frame count", () => nextFrame = 300); sendFrames(); - waitForPlayer(); - AddUntilStep("playing from correct point in time", () => player.ChildrenOfType().First().FrameStableClock.CurrentTime > 30000); } From 662bbed5d1206ca918d54a803ea52825ade0432d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Jun 2021 17:38:51 +0900 Subject: [PATCH 0375/1621] Fix seeking to gameplay too soon --- osu.Game/Screens/Play/SpectatorPlayer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 605702c8e2..1e6becff40 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -43,9 +43,9 @@ namespace osu.Game.Screens.Play }); } - protected override void LoadComplete() + protected override void StartGameplay() { - base.LoadComplete(); + base.StartGameplay(); spectatorClient.OnNewFrames += userSentFrames; seekToGameplay(); From be03a2d7d2dfa19a9f8fc1fe43aeb1c00f7c59f5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Jun 2021 17:47:22 +0900 Subject: [PATCH 0376/1621] Fix multiple calls to seek method potentially not working --- osu.Game/Screens/Play/Player.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 64d5aa6ef7..985eb32cca 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -583,6 +583,8 @@ namespace osu.Game.Screens.Play /// The destination time to seek to. public void Seek(double time) => GameplayClockContainer.Seek(time); + private ScheduledDelegate frameStablePlaybackResetDelegate; + /// /// Seeks to a specific time in gameplay, bypassing frame stability. /// @@ -592,13 +594,16 @@ namespace osu.Game.Screens.Play /// The destination time to seek to. public void NonFrameStableSeek(double time) { + if (frameStablePlaybackResetDelegate?.Cancelled == false && !frameStablePlaybackResetDelegate.Completed) + frameStablePlaybackResetDelegate.RunTask(); + bool wasFrameStable = DrawableRuleset.FrameStablePlayback; DrawableRuleset.FrameStablePlayback = false; Seek(time); // Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek. - ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable); + frameStablePlaybackResetDelegate = ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable); } /// @@ -931,11 +936,10 @@ namespace osu.Game.Screens.Play /// Creates the player's . /// /// The . - protected virtual Score CreateScore() => - new Score - { - ScoreInfo = new ScoreInfo { User = api.LocalUser.Value }, - }; + protected virtual Score CreateScore() => new Score + { + ScoreInfo = new ScoreInfo { User = api.LocalUser.Value }, + }; /// /// Imports the player's to the local database. From e887807ae7f186b03dad7525aa97b56ab1f586bb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Jun 2021 18:32:05 +0900 Subject: [PATCH 0377/1621] Apply review fixes --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 6 +++--- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index bd6da52654..2d171eb533 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -68,9 +68,6 @@ namespace osu.Game.Rulesets.UI private bool frameStablePlayback = true; - /// - /// Whether to enable frame-stable playback. - /// internal override bool FrameStablePlayback { get => frameStablePlayback; @@ -425,6 +422,9 @@ namespace osu.Game.Rulesets.UI /// public abstract IFrameStableClock FrameStableClock { get; } + /// + /// Whether to enable frame-stable playback. + /// internal abstract bool FrameStablePlayback { get; set; } /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 985eb32cca..a4aaeb5b1f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -592,7 +592,7 @@ namespace osu.Game.Screens.Play /// Intermediate hitobject judgements may not be applied or reverted correctly during this seek. /// /// The destination time to seek to. - public void NonFrameStableSeek(double time) + internal void NonFrameStableSeek(double time) { if (frameStablePlaybackResetDelegate?.Cancelled == false && !frameStablePlaybackResetDelegate.Completed) frameStablePlaybackResetDelegate.RunTask(); From 9ad87ee5dc421eb1208d07b1d4b5356a130a383c Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 27 May 2021 15:04:22 +0900 Subject: [PATCH 0378/1621] 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 0379/1621] 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 0380/1621] 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 0381/1621] 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 0382/1621] 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 0383/1621] 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 0384/1621] 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 8193691cbcaed3f84383e595b9428dd395e3348e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Jun 2021 13:58:19 +0200 Subject: [PATCH 0385/1621] Invert condition to reduce nesting --- osu.Game/Online/Chat/ChannelManager.cs | 42 +++++++++++++------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index f6804fdbf7..cf0fd2cbe6 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -471,29 +471,29 @@ namespace osu.Game.Online.Chat ClosedChannel lastClosedChannel = closedChannels.Last(); closedChannels.RemoveAt(closedChannels.Count - 1); - // If the user hasn't already joined the channel, try to join it - if (joinedChannels.FirstOrDefault(lastClosedChannel.IsEqual) == null) + // If the user has already joined the channel, try the next one + if (joinedChannels.FirstOrDefault(lastClosedChannel.IsEqual) != null) + continue; + + Channel lastChannel = AvailableChannels.FirstOrDefault(lastClosedChannel.IsEqual); + + if (lastChannel != null) { - Channel lastChannel = AvailableChannels.FirstOrDefault(lastClosedChannel.IsEqual); - - if (lastChannel != null) - { - // Channel exists as an availaable channel, directly join it - CurrentChannel.Value = JoinChannel(lastChannel); - } - else if (lastClosedChannel.Type == ChannelType.PM) - { - // Try to get User to open PM chat - users.GetUserAsync((int)lastClosedChannel.Id).ContinueWith(u => - { - if (u.Result == null) return; - - Schedule(() => CurrentChannel.Value = JoinChannel(new Channel(u.Result))); - }); - } - - return; + // Channel exists as an availaable channel, directly join it + CurrentChannel.Value = JoinChannel(lastChannel); } + else if (lastClosedChannel.Type == ChannelType.PM) + { + // Try to get User to open PM chat + users.GetUserAsync((int)lastClosedChannel.Id).ContinueWith(u => + { + if (u.Result == null) return; + + Schedule(() => CurrentChannel.Value = JoinChannel(new Channel(u.Result))); + }); + } + + return; } } From e8c2483f19c17197552f67135773888870d0e294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Jun 2021 13:59:25 +0200 Subject: [PATCH 0386/1621] Use standard list instead of bindable list No reason to use a bindable list there, as `CollectionChanged` was never subscribed to. --- osu.Game/Online/Chat/ChannelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index cf0fd2cbe6..617471f75f 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -37,7 +37,7 @@ namespace osu.Game.Online.Chat /// /// Keeps a stack of recently closed channels /// - private readonly BindableList closedChannels = new BindableList(); + private readonly List closedChannels = new List(); // For efficiency purposes, this constant bounds the number of closed channels we store. // This number is somewhat arbitrary; future developers are free to modify it. From b2cc2a51ec112ef2834b2f566d75375b3b4781ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Jun 2021 14:13:01 +0200 Subject: [PATCH 0387/1621] Rename method to be less misleading Would rather avoid variations of Equals/Equal/IsEqual. There's not really much equality involved as the types are different. --- osu.Game/Online/Chat/ChannelManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 617471f75f..bdce366c06 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -472,10 +472,10 @@ namespace osu.Game.Online.Chat closedChannels.RemoveAt(closedChannels.Count - 1); // If the user has already joined the channel, try the next one - if (joinedChannels.FirstOrDefault(lastClosedChannel.IsEqual) != null) + if (joinedChannels.FirstOrDefault(lastClosedChannel.Matches) != null) continue; - Channel lastChannel = AvailableChannels.FirstOrDefault(lastClosedChannel.IsEqual); + Channel lastChannel = AvailableChannels.FirstOrDefault(lastClosedChannel.Matches); if (lastChannel != null) { @@ -596,7 +596,7 @@ namespace osu.Game.Online.Chat Id = id; } - public bool IsEqual(Channel channel) + public bool Matches(Channel channel) { if (channel.Type != Type) return false; From 5f5f3a8c5c17a3a634ff49e9ce0bfb086ec4e3c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Jun 2021 14:20:52 +0200 Subject: [PATCH 0388/1621] General comment cleanups --- osu.Game/Online/Chat/ChannelManager.cs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index bdce366c06..8507887357 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -441,7 +441,7 @@ namespace osu.Game.Online.Chat closedChannels.RemoveAt(0); } - // For PM channels, we store the user ID; else, we store the channel id + // For PM channels, we store the user ID; else, we store the channel ID closedChannels.Add(channel.Type == ChannelType.PM ? new ClosedChannel(ChannelType.PM, channel.Users.Single().Id) : new ClosedChannel(channel.Type, channel.Id)); @@ -454,18 +454,14 @@ namespace osu.Game.Online.Chat }); /// - /// Opens the most recently closed channel that has not - /// already been reopened + /// Opens the most recently closed channel that has not already been reopened, /// Works similarly to reopening the last closed tab on a web browser. /// public void JoinLastClosedChannel() { - // This loop could be eliminated if a check was added so that - // when the code opens a channel it removes from the closedChannel list. - // However, this would require adding an O(|closeChannels|) work operation - // every time the user joins a channel, which would make joining a channel - // slower. We wanted to centralize all major slowdowns so they - // can only occur if the user actually decides to use this feature. + // This loop could be eliminated if the join channel operation ensured that every channel joined + // is removed from the closedChannels list, but it'd require a linear scan of closed channels on every join. + // To keep the overhead of joining channels low, just lazily scan the list of closed channels locally. while (closedChannels.Count > 0) { ClosedChannel lastClosedChannel = closedChannels.Last(); @@ -479,12 +475,12 @@ namespace osu.Game.Online.Chat if (lastChannel != null) { - // Channel exists as an availaable channel, directly join it + // Channel exists as an available channel, directly join it CurrentChannel.Value = JoinChannel(lastChannel); } else if (lastClosedChannel.Type == ChannelType.PM) { - // Try to get User to open PM chat + // Try to get user in order to open PM chat users.GetUserAsync((int)lastClosedChannel.Id).ContinueWith(u => { if (u.Result == null) return; @@ -583,7 +579,7 @@ namespace osu.Game.Online.Chat } /// - /// Class that stores information about a closed channel + /// Stores information about a closed channel /// public class ClosedChannel { From e117f98bfaf099bde18bc72a66007d43348deee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Jun 2021 14:31:13 +0200 Subject: [PATCH 0389/1621] Rename test steps --- .../Visual/Online/TestSceneChatOverlay.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index c40b60c35d..3971146ff8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -246,7 +246,7 @@ namespace osu.Game.Tests.Visual.Online // Want to close channel 2 AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); - AddStep("Press keys", pressCloseDocumentKeys); + AddStep("Close tab via shortcut", pressCloseDocumentKeys); // Channel 2 should be closed AddAssert("Channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); @@ -255,7 +255,7 @@ namespace osu.Game.Tests.Visual.Online // Want to close channel 1 AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - AddStep("Press keys", pressCloseDocumentKeys); + AddStep("Close tab via shortcut", pressCloseDocumentKeys); // Channel 1 and channel 2 should be closed AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); } @@ -270,7 +270,7 @@ namespace osu.Game.Tests.Visual.Online }); // Want to join another channel - AddStep("Press keys", pressNewTabKeys); + AddStep("Press new tab shortcut", pressNewTabKeys); // Selector should be visible AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); @@ -287,7 +287,7 @@ namespace osu.Game.Tests.Visual.Online }); // Should do nothing - AddStep("Press keys", pressRestoreTabKeys); + AddStep("Restore tab via shortcut", pressRestoreTabKeys); AddAssert("All channels still open", () => channelManager.JoinedChannels.Count == 3); // Close channel 1 @@ -297,7 +297,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Other channels still open", () => channelManager.JoinedChannels.Count == 2); // Reopen channel 1 - AddStep("Press keys", pressRestoreTabKeys); + AddStep("Restore tab via shortcut", pressRestoreTabKeys); AddAssert("All channels now open", () => channelManager.JoinedChannels.Count == 3); AddAssert("Current channel is channel 1", () => currentChannel == channel1); @@ -310,13 +310,13 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Current channel is channel 3", () => currentChannel == channel3); // Should first re-open channel 2 - AddStep("Press keys", pressRestoreTabKeys); + AddStep("Restore tab via shortcut", pressRestoreTabKeys); AddAssert("Channel 1 still closed", () => !channelManager.JoinedChannels.Contains(channel1)); AddAssert("Channel 2 now open", () => channelManager.JoinedChannels.Contains(channel2)); AddAssert("Current channel is channel 2", () => currentChannel == channel2); // Should then re-open channel 1 - AddStep("Press keys", pressRestoreTabKeys); + AddStep("Restore tab via shortcut", pressRestoreTabKeys); AddAssert("All channels now open", () => channelManager.JoinedChannels.Count == 3); AddAssert("Current channel is channel 1", () => currentChannel == channel1); } From 490ce0bbc542c4324c2902e7311fd9b8fc55424b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 3 Jun 2021 19:40:54 +0700 Subject: [PATCH 0390/1621] load placeholder page when failed --- osu.Game/Overlays/WikiOverlay.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index af7bc40f17..f26162151f 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -92,7 +92,7 @@ namespace osu.Game.Overlays Loading.Show(); request.Success += response => Schedule(() => onSuccess(response)); - request.Failure += _ => Schedule(() => LoadDisplay(Empty())); + request.Failure += _ => Schedule(onFail); api.PerformAsync(request); } @@ -132,6 +132,24 @@ namespace osu.Game.Overlays } } + private void onFail() + { + LoadDisplay(new WikiMarkdownContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + CurrentPath = $@"{api.WebsiteRootUrl}/wiki/", + Text = "There is something wrong when fetching this page. [Back to main page.](Main_Page)", + DocumentMargin = new MarginPadding(0), + DocumentPadding = new MarginPadding + { + Vertical = 20, + Left = 30, + Right = 50, + }, + }); + } + private void showParentPage() { var parentPath = string.Join("/", path.Value.Split('/').SkipLast(1)); From b36c406a83b35721e72c37ecd3447f0ba5541daf Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 3 Jun 2021 19:48:04 +0700 Subject: [PATCH 0391/1621] add test scene for error page --- .../Visual/Online/TestSceneWikiOverlay.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index da4bf82948..4d09ed21dc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.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.Net; using NUnit.Framework; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -32,7 +33,14 @@ namespace osu.Game.Tests.Visual.Online AddStep("Show Article Page", () => wiki.ShowPage("Interface")); } - private void setUpWikiResponse(APIWikiPage r) + [Test] + public void TestErrorPage() + { + setUpWikiResponse(null, true); + AddStep("Show Error Page", () => wiki.ShowPage("Error")); + } + + private void setUpWikiResponse(APIWikiPage r, bool isFailed = false) => AddStep("set up response", () => { dummyAPI.HandleRequest = request => @@ -40,7 +48,11 @@ namespace osu.Game.Tests.Visual.Online if (!(request is GetWikiRequest getWikiRequest)) return false; - getWikiRequest.TriggerSuccess(r); + if (isFailed) + getWikiRequest.TriggerFailure(new WebException()); + else + getWikiRequest.TriggerSuccess(r); + return true; }; }); From c5fc155cc0dfd99010fe62ad57fb30e055ccddf8 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 3 Jun 2021 20:37:27 +0700 Subject: [PATCH 0392/1621] Change text wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Overlays/WikiOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index f26162151f..2261f71c04 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -139,7 +139,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, CurrentPath = $@"{api.WebsiteRootUrl}/wiki/", - Text = "There is something wrong when fetching this page. [Back to main page.](Main_Page)", + Text = $"Something went wrong when trying to fetch page \"{path.Value}\".\n\n[Go back to main page.](Main_Page)", DocumentMargin = new MarginPadding(0), DocumentPadding = new MarginPadding { From 7a4fc9ffc8501be688a533fc619198936c9ae8a2 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Thu, 3 Jun 2021 18:16:11 +0200 Subject: [PATCH 0393/1621] Move seed to base class --- .../Mods/ManiaModRandom.cs | 6 +- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 90 ------------------ .../Mods/TaikoModRandom.cs | 5 +- osu.Game/Rulesets/Mods/ModRandom.cs | 91 +++++++++++++++++++ 4 files changed, 100 insertions(+), 92 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index 699c58c373..6f2d4fe91e 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.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.Linq; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Utils; @@ -17,8 +18,11 @@ namespace osu.Game.Rulesets.Mania.Mods public void ApplyToBeatmap(IBeatmap beatmap) { + Seed.Value ??= RNG.Next(); + var rng = new Random((int)Seed.Value); + var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns; - var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => RNG.Next()).ToList(); + var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => rng.Next()).ToList(); beatmap.HitObjects.OfType().ForEach(h => h.Column = shuffledColumns[h.Column]); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 7b28675511..4dfadbb835 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -3,15 +3,9 @@ using System; using System.Linq; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; @@ -42,13 +36,6 @@ namespace osu.Game.Rulesets.Osu.Mods private Random rng; - [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(OsuModRandomSettingsControl))] - public Bindable Seed { get; } = new Bindable - { - Default = null, - Value = null - }; - public void ApplyToBeatmap(IBeatmap beatmap) { if (!(beatmap is OsuBeatmap osuBeatmap)) @@ -289,82 +276,5 @@ namespace osu.Game.Rulesets.Osu.Mods AngleRad = 0; } } - - public class OsuModRandomSettingsControl : SettingsItem - { - protected override Drawable CreateControl() => new SeedControl - { - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 5 } - }; - - private sealed class SeedControl : CompositeDrawable, IHasCurrentValue - { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current - { - get => current; - set - { - current.Current = value; - seedNumberBox.Text = value.Value.ToString(); - } - } - - private readonly OsuNumberBox seedNumberBox; - - public SeedControl() - { - AutoSizeAxes = Axes.Y; - - InternalChildren = new[] - { - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), - new Dimension(GridSizeMode.Relative, 0.25f) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - seedNumberBox = new OsuNumberBox - { - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true - } - } - } - } - }; - - seedNumberBox.Current.BindValueChanged(e => - { - int? value = null; - - if (int.TryParse(e.NewValue, out var intVal)) - value = intVal; - - current.Value = value; - }); - } - - protected override void Update() - { - if (current.Value == null) - seedNumberBox.Text = current.Current.Value.ToString(); - } - } - } } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs index a22f189d5e..307a37bf2e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs @@ -20,10 +20,13 @@ namespace osu.Game.Rulesets.Taiko.Mods { var taikoBeatmap = (TaikoBeatmap)beatmap; + Seed.Value ??= RNG.Next(); + var rng = new Random((int)Seed.Value); + foreach (var obj in taikoBeatmap.HitObjects) { if (obj is Hit hit) - hit.Type = RNG.Next(2) == 0 ? HitType.Centre : HitType.Rim; + hit.Type = rng.Next(2) == 0 ? HitType.Centre : HitType.Rim; } } } diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index da55ab3fbf..3f14263420 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -1,8 +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 osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; 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 { @@ -13,5 +20,89 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.Conversion; public override IconUsage? Icon => OsuIcon.Dice; public override double ScoreMultiplier => 1; + + [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(ModRandomSettingsControl))] + public Bindable Seed { get; } = new Bindable + { + Default = null, + Value = null + }; + + private class ModRandomSettingsControl : SettingsItem + { + protected override Drawable CreateControl() => new SeedControl + { + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 5 } + }; + + private sealed class SeedControl : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current; + set + { + current.Current = value; + seedNumberBox.Text = value.Value.ToString(); + } + } + + private readonly OsuNumberBox seedNumberBox; + + public SeedControl() + { + AutoSizeAxes = Axes.Y; + + InternalChildren = new[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(GridSizeMode.Relative, 0.25f) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + seedNumberBox = new OsuNumberBox + { + RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true + } + } + } + } + }; + + seedNumberBox.Current.BindValueChanged(e => + { + int? value = null; + + if (int.TryParse(e.NewValue, out var intVal)) + value = intVal; + + current.Value = value; + }); + } + + protected override void Update() + { + if (current.Value == null) + seedNumberBox.Text = current.Current.Value.ToString(); + } + } + } } } From d444fed46f1169eb20b5041f29b22ae39d867375 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 01:59:56 +0900 Subject: [PATCH 0394/1621] Detach gameplay score from replay recorder before importing Closes #13320. --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 8 +++++++- osu.Game/Rulesets/UI/RulesetInputManager.cs | 6 ++++-- osu.Game/Screens/Play/Player.cs | 3 +++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 16a411d478..e2e3c22618 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -267,6 +267,12 @@ namespace osu.Game.Rulesets.UI if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputManager)) throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports recording is not available"); + if (score == null) + { + recordingInputManager.Recorder = null; + return; + } + var recorder = CreateReplayRecorder(score); if (recorder == null) @@ -511,7 +517,7 @@ namespace osu.Game.Rulesets.UI /// Sets a replay to be used to record gameplay. /// /// The target to be recorded to. - public abstract void SetRecordTarget(Score score); + public abstract void SetRecordTarget([CanBeNull] Score score); /// /// Invoked when the interactive user requests resuming from a paused state. diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 75c3a4661c..e3b9ad5641 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -30,12 +30,14 @@ namespace osu.Game.Rulesets.UI { set { - if (recorder != null) + if (value != null && recorder != null) throw new InvalidOperationException("Cannot attach more than one recorder"); + recorder?.Expire(); recorder = value; - KeyBindingContainer.Add(recorder); + if (recorder != null) + KeyBindingContainer.Add(recorder); } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0d1ebd30fc..20012d0282 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -636,6 +636,9 @@ namespace osu.Game.Screens.Play ValidForResume = false; + // 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; prepareScoreForDisplayTask ??= Task.Run(async () => From 076e498a6378b07dcae67e4b06bfde1d0f6f565c Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 00:12:13 +0700 Subject: [PATCH 0395/1621] create abstract class OverlaySidebar --- osu.Game/Overlays/OverlaySidebar.cs | 76 +++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 osu.Game/Overlays/OverlaySidebar.cs diff --git a/osu.Game/Overlays/OverlaySidebar.cs b/osu.Game/Overlays/OverlaySidebar.cs new file mode 100644 index 0000000000..468b5b6eb3 --- /dev/null +++ b/osu.Game/Overlays/OverlaySidebar.cs @@ -0,0 +1,76 @@ +// 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.Shapes; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays +{ + public abstract class OverlaySidebar : CompositeDrawable + { + private readonly Box sidebarBackground; + private readonly Box scrollbarBackground; + + protected OverlaySidebar() + { + RelativeSizeAxes = Axes.Y; + Width = 250; + InternalChildren = new Drawable[] + { + sidebarBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + scrollbarBackground = new Box + { + RelativeSizeAxes = Axes.Y, + Width = OsuScrollContainer.SCROLL_BAR_HEIGHT, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Alpha = 0.5f + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = -3 }, // Compensate for scrollbar margin + Child = new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 3 }, // Addeded 3px back + Child = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Vertical = 20, + Left = 50, + Right = 30 + }, + Child = CreateContent() + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + sidebarBackground.Colour = colourProvider.Background4; + scrollbarBackground.Colour = colourProvider.Background3; + } + + [NotNull] + protected virtual Drawable CreateContent() => Empty(); + } +} From 905472a20b5c5b1380efc4afb96b315939c8cf3a Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 00:12:29 +0700 Subject: [PATCH 0396/1621] make NewsSidebar extends OverlaySidebar --- osu.Game/Overlays/News/Sidebar/NewsSidebar.cs | 81 ++++--------------- 1 file changed, 15 insertions(+), 66 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs index 9e397e78c8..35cd6eb03b 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs @@ -6,87 +6,36 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Game.Online.API.Requests.Responses; -using osu.Framework.Graphics.Shapes; using osuTK; using System.Linq; -using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.News.Sidebar { - public class NewsSidebar : CompositeDrawable + public class NewsSidebar : OverlaySidebar { [Cached] public readonly Bindable Metadata = new Bindable(); private FillFlowContainer monthsFlow; - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + protected override Drawable CreateContent() => new FillFlowContainer { - RelativeSizeAxes = Axes.Y; - Width = 250; - InternalChildren = new Drawable[] + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 20), + Children = new Drawable[] { - new Box + new YearsPanel(), + monthsFlow = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4 - }, - new Box - { - RelativeSizeAxes = Axes.Y, - Width = OsuScrollContainer.SCROLL_BAR_HEIGHT, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Colour = colourProvider.Background3, - Alpha = 0.5f - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = -3 }, // Compensate for scrollbar margin - Child = new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Right = 3 }, // Addeded 3px back - Child = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding - { - Vertical = 20, - Left = 50, - Right = 30 - }, - Child = new FillFlowContainer - { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0, 20), - Children = new Drawable[] - { - new YearsPanel(), - monthsFlow = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10) - } - } - } - } - } - } + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10) } - }; - } + } + }; protected override void LoadComplete() { From beb0119dd54c03d0258a3f5174b7face1bf5b7b0 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 09:28:31 +0700 Subject: [PATCH 0397/1621] initial wiki sidebar --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 osu.Game/Overlays/Wiki/WikiSidebar.cs diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs new file mode 100644 index 0000000000..6d1f520135 --- /dev/null +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -0,0 +1,9 @@ +// 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.Overlays.Wiki +{ + public class WikiSidebar : OverlaySidebar + { + } +} From 791a9dd33a3a6754170457a9da701f776b4c7958 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 09:29:10 +0700 Subject: [PATCH 0398/1621] add WikiArticlePage --- osu.Game/Overlays/Wiki/WikiArticlePage.cs | 54 +++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 osu.Game/Overlays/Wiki/WikiArticlePage.cs diff --git a/osu.Game/Overlays/Wiki/WikiArticlePage.cs b/osu.Game/Overlays/Wiki/WikiArticlePage.cs new file mode 100644 index 0000000000..c41ab8b250 --- /dev/null +++ b/osu.Game/Overlays/Wiki/WikiArticlePage.cs @@ -0,0 +1,54 @@ +// 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.Overlays.Wiki.Markdown; + +namespace osu.Game.Overlays.Wiki +{ + public class WikiArticlePage : GridContainer + { + public Container SidebarContainer { get; } + + public WikiArticlePage(string currentPath, string markdown) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + }; + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }; + Content = new[] + { + new Drawable[] + { + SidebarContainer = new Container + { + AutoSizeAxes = Axes.X, + Child = new WikiSidebar(), + }, + new WikiMarkdownContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + CurrentPath = currentPath, + Text = markdown, + DocumentMargin = new MarginPadding(0), + DocumentPadding = new MarginPadding + { + Vertical = 20, + Left = 30, + Right = 50, + }, + } + }, + }; + } + } +} From 458910b7446e618ef132a1e5b0cf4b2e92d8b519 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 09:29:36 +0700 Subject: [PATCH 0399/1621] use WikiArticlePage in WikiOverlay --- osu.Game/Overlays/WikiOverlay.cs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index c5ba0eed66..4cfbbbbfe4 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.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.Linq; using System.Threading; using osu.Framework.Allocation; @@ -10,7 +11,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Wiki; -using osu.Game.Overlays.Wiki.Markdown; namespace osu.Game.Overlays { @@ -31,6 +31,8 @@ namespace osu.Game.Overlays private bool displayUpdateRequired = true; + private WikiArticlePage articlePage; + public WikiOverlay() : base(OverlayColourScheme.Orange, false) { @@ -82,6 +84,17 @@ namespace osu.Game.Overlays }, (cancellationToken = new CancellationTokenSource()).Token); } + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + if (articlePage != null) + { + articlePage.SidebarContainer.Height = DrawHeight; + articlePage.SidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0)); + } + } + private void onPathChanged(ValueChangedEvent e) { cancellationToken?.Cancel(); @@ -115,20 +128,7 @@ namespace osu.Game.Overlays } else { - LoadDisplay(new WikiMarkdownContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - CurrentPath = $@"{api.WebsiteRootUrl}/wiki/{path.Value}/", - Text = response.Markdown, - DocumentMargin = new MarginPadding(0), - DocumentPadding = new MarginPadding - { - Vertical = 20, - Left = 30, - Right = 50, - }, - }); + LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/{path.Value}/", response.Markdown)); } } From 34379b953af958c1a8ac2634cf1bdf9bb3fb72fb Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 09:36:21 +0700 Subject: [PATCH 0400/1621] change test scene response --- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 4d09ed21dc..3506d459ce 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Online public void TestArticlePage() { setUpWikiResponse(responseArticlePage); - AddStep("Show Article Page", () => wiki.ShowPage("Interface")); + AddStep("Show Article Page", () => wiki.ShowPage("Article_styling_criteria/Formatting")); } [Test] @@ -69,16 +69,16 @@ namespace osu.Game.Tests.Visual.Online "---\nlayout: main_page\n---\n\n\n\n
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n", }; - // From https://osu.ppy.sh/api/v2/wiki/en/Interface + // From https://osu.ppy.sh/api/v2/wiki/en/Article_styling_criteria/Formatting private APIWikiPage responseArticlePage => new APIWikiPage { - Title = "Interface", + Title = "Formatting", Layout = "markdown_page", - Path = "Interface", + Path = "Article_styling_criteria/Formatting", Locale = "en", - Subtitle = null, + Subtitle = "Article styling criteria", Markdown = - "# Interface\n\n![](img/intro-screen.jpg \"Introduction screen\")\n\n## Main Menu\n\n![](img/main-menu.jpg \"Main Menu\")\n\nThe [osu!cookie](/wiki/Glossary#cookie) \\[1\\] pulses according to the [BPM](/wiki/Beatmapping/Beats_per_minute) of any song currently playing on the main menu. In addition, bars will extend out of the osu!cookie in accordance to the song's volume. If no song is playing, it pulses at a slow 60 BPM. The elements of the main menu are as follows:\n\n- \\[2\\] Click Play (`P`) or the logo to switch to the Solo mode song selection screen.\n- \\[3\\] Click Edit (`E`) to open the Editor mode song selection screen.\n- \\[4\\] Click Options (`O`) to go to the Options screen.\n- \\[5\\] Click Exit (`Esc`) to exit osu!.\n- \\[6\\] A random useful tip is displayed below the menu.\n- \\[7\\] In the lower-left is a link to the osu! website, as well as copyright information.\n- \\[8\\] Connection result to [Bancho](/wiki/Glossary#bancho)! In this picture it is not shown, but the connection result looks like a chain link.\n- \\[9\\] In the bottom right are the chat controls for the extended [chat window](/wiki/Chat_Console) (called \"Player List\" here) and the regular chat window (`F9` & `F8`, respectively).\n- \\[10\\] In the upper right is the osu! jukebox which plays the songs in random order. The top shows the song currently playing. The buttons, from left to right, do as follows:\n - Previous Track\n - Play\n - Pause\n - Stop (the difference between Play and Stop is that Stop will reset the song to the beginning, while Pause simply pauses it)\n - Next Track\n - View Song Info. This toggles the top bar showing the song info between being permanent and temporary. When permanent, the info bar will stay visible until it fades out with the rest of the UI. When temporary, it will disappear a little while after a song has been chosen. It will stay hidden until it is toggled again, or another song plays.\n- \\[11\\] The number of beatmaps you have available, how long your osu!client has been running, and your system clock.\n- \\[12\\] Your profile, click on it to display the User Options (see below).\n\n## User Options\n\n![](img/user-options.jpg \"User Options\")\n\nAccess this screen by clicking your profile at the top left of the main menu. You cannot access the Chat Consoles while viewing the user option screen. You can select any item by pressing the corresponding number on the option:\n\n1. `View Profile`: Opens up your profile page in your default web browser.\n2. `Sign Out`: Sign out of your account (after signing out, the [Options](/wiki/Options) sidebar will prompt you to sign in).\n3. `Change Avatar`: Open up the edit avatar page in your default web browser.\n4. `Close`: Close this dialog\n\n## Play Menu\n\n![](img/play-menu.jpg \"Play Menu\")\n\n- Click `Solo` (`P`) to play alone.\n- Click `Multi` (`M`) to play with other people. You will be directed to the [Multi](/wiki/Multi) Lobby (see below).\n- Click `Back` to return to the main menu.\n\n## Multi Lobby\n\n*Main page: [Multi](/wiki/Multi)*\n\n![](img/multi-lobby.jpg \"Multi Lobby\")\n\n![](img/multi-room.jpg \"Multi Host\")\n\n1. Your rank in the match. This is also shown next to your name.\n2. Your profile information.\n3. The jukebox.\n4. Player list - displays player names, their rank (host or player), their [mods](/wiki/Game_modifier) activated (if any, see \\#7), their osu! ranking, and their team (if applicable).\n5. The name of the match and the password settings.\n6. The beatmap selected. It shows the beatmap as it would in the solo song selection screen.\n7. The [mods](/wiki/Game_modifier) that you have activated (see #12), as well as the option to select them. The option marked \"Free Mods\" toggles whether or not players can select their own mods. If yes, they can pick any combination of mods *except for speed-altering mods like [Double Time](/wiki/Game_modifier/Double_Time)*. If no, the host decides what mods will be used. The host can pick speed-altering mods regardless of whether or not Free Mods is turned on.\n8. The team mode and win conditions.\n9. The ready button.\n10. The [chat console](/wiki/Chat_Console).\n11. The leave button.\n12. Where your activated mods appear.\n\n## Song Selection Screen\n\n![](img/song-selection.jpg \"Song Selection\")\n\nYou can identify the current mode selected by either looking at the icon in the bottom left, above Mode, or by looking at the transparent icon in the center of the screen. These are the four you will see:\n\n- ![](/wiki/shared/mode/osu.png) is [osu!](/wiki/Game_mode/osu!)\n- ![](/wiki/shared/mode/taiko.png) is [osu!taiko](/wiki/Game_mode/osu!taiko)\n- ![](/wiki/shared/mode/catch.png) is [osu!catch](/wiki/Game_mode/osu!catch)\n- ![](/wiki/shared/mode/mania.png) is [osu!mania](/wiki/Game_mode/osu!mania)\n\nBefore continuing on, this screen has too many elements to note with easily, noticeable numbers. The subsections below will focus on one part of the screen at a time, starting from the top down and left to right.\n\n### Beatmap Information\n\n![](img/metadata-comparison.jpg)\n\n![](img/beatmap-metadata.jpg)\n\nThis area displays **information on the beatmap difficulty currently selected.** By default, the beatmap whose song is heard in the osu! jukebox is selected when entering the selection screen. In the top left is the ranked status of the beatmap. The title is next. Normally, the romanised title is shown, but if you select `Prefer metadata in original language` in the [Options](/wiki/Options), it will show the Unicode title; this is shown in the upper picture. The beatmapper is also shown, and beatmap information is shown below. From left to right, the values are as follows:\n\n- **Length**: The total length of the beatmap, from start to finish and including breaks. Not to be confused with [drain time](/wiki/Glossary#drain-time).\n- **BPM**: The BPM of the beatmap. If (like in the lower picture) there are two BPMS and one in parentheses, this means that the BPM changes throughout the song. It shows the slowest and fastest BPMs, and the value in parentheses is the BPM at the start of the beatmap.\n- **Objects**: The total amount of [hit objects](/wiki/Hit_Objects) in the beatmap.\n- **Circles**: The total amount of hit circles in the beatmap.\n- **Sliders**: The total amount of sliders in the beatmap.\n- **Spinners**: The total amount of spinners in the beatmap.\n- **OD**: The Overall Difficulty of the beatmap.\n- **HP**: The drain rate of your HP. In osu!, this is how much of an HP loss you receive upon missing a note, how fast the life bar idly drains, and how much HP is received for hitting a note. In osu!mania, this is the same except there is no idle HP drain. In osu!taiko, this determines how slowly the HP bar fills and how much HP is lost when a note is missed. osu!catch is the same as osu!.\n- **Stars**: The star difficulty of the beatmap. This is graphically visible in the beatmap rectangle itself.\n\n### Group and Sort\n\n![](img/beatmap-filters.jpg)\n\nClick on one of the tabs to **sort your song list according to the selected criterion**.\n\n**Group** - Most options organize beatmaps into various expandable groups:\n\n- `No grouping` - Beatmaps will not be grouped but will still be sorted in the order specified by Sort.\n- `By Difficulty` - Beatmaps will be grouped by their star difficulty, rounded to the nearest whole number.\n- `By Artist` - Beatmaps will be grouped by the artist's first character of their name.\n- `Recently Played` - Beatmaps will be grouped by when you last played them.\n- `Collections` - This will show the collections you have created. *Note that this will hide beatmaps not listed in a collection!*\n- `By BPM` - Beatmaps will be grouped according to BPM in multiples of 60, starting at 120.\n- `By Creator` - Beatmaps will be grouped by the beatmap creator's name's first character.\n- `By Date Added` - Beatmaps will be grouped according to when they were added, from today to 4+ months ago.\n- `By Length` - Beatmaps will be grouped according to their length: 1 minute or less, 2 minutes or less, 3, 4, 5, and 10.\n- `By Mode` - Beatmaps will be grouped according to their game mode.\n- `By Rank Achieved` - Beatmaps will be sorted by the highest rank achieved on them.\n- `By Title` - Beatmaps will be grouped by the first letter of their title.\n- `Favourites` - Only beatmaps you have favorited online will be shown.\n- `My Maps` - Only beatmaps you have mapped (that is, whose creator matches your profile name) will be shown.\n- `Ranked Status` - Beatmaps will be grouped by their ranked status: ranked, pending, not submitted, unknown, or loved.\n\nThe first five groupings are available in tabs below Group and Sort.\n\n**Sort** - Sorts beatmaps in a certain order\n\n- `By Artist` - Beatmaps will be sorted alphabetically by the artist's name's first character.\n- `By BPM` - Beatmaps will be sorted lowest to highest by their BPM. For maps with multiple BPMs, the highest will be used.\n- `By Creator` - Beatmaps will be sorted alphabetically by the creator's name's first character.\n- `By Date Added` - Beatmaps will be sorted from oldest to newest by when they were added.\n- `By Difficulty` - Beatmaps will be sorted from easiest to hardest by star difficulty. *Note that this will split apart mapsets!*\n- `By Length` - Beatmaps will be sorted from shortest to longest by length.\n- `By Rank Achieved` - Beatmaps will be sorted from poorest to best by the highest rank achieved on them.\n- `By Title` - Beatmaps will be sorted alphabetically by the first character of their name.\n\n### Search\n\n![](img/search-bar.jpg)\n\n*Note: You cannot have the chat console or the options sidebar open if you want to search; otherwise, anything you type will be perceived as chat text or as an options search query.*\n\nOnly beatmaps that match the criteria of your search will be shown. By default, any search will be matched against the beatmaps' artists, titles, creators, and tags.\n\nIn addition to searching these fields, you can use filters to search through other metadata by combining one of the supported filters with a comparison to a value (for example, `ar=9`).\n\nSupported filters:\n\n- `artist`: Name of the artist\n- `creator`: Name of the beatmap creator\n- `ar`: Approach Rate\n- `cs`: Circle Size\n- `od`: Overall Difficulty\n- `hp`: HP Drain Rate\n- `keys`: Number of keys (osu!mania and converted beatmaps only)\n- `stars`: Star Difficulty\n- `bpm`: Beats per minute\n- `length`: Length in seconds\n- `drain`: Drain Time in seconds\n- `mode`: Mode. Value can be `osu`, `taiko`, `catchthebeat`, or `mania`, or `o`/`t`/`c`/`m` for short.\n- `status`: Ranked status. Value can be `ranked`, `approved`, `pending`, `notsubmitted`, `unknown`, or `loved`, or `r`/`a`/`p`/`n`/`u`/`l` for short.\n- `played`: Time since last played in days\n- `unplayed`: Shows only unplayed maps. A comparison with no set value must be used. The comparison itself is ignored.\n- `speed`: Saved osu!mania scroll speed. Always 0 for unplayed maps or if the [Remember osu!mania scroll speed per beatmap](/wiki/Options#gameplay) option is off\n\nSupported comparisons:\n\n- `=` or `==`: Equal to\n- `!=`: Not equal to\n- `<`: Less than\n- `>`: Greater than\n- `<=`: Less than or equal to\n- `>=`: Greater than or equal to\n\nYou may also enter a beatmap or beatmapset ID in your search to get a single result.\n\n### Rankings\n\n![](img/leaderboards.jpg)\n\n A variety of things can appear in this space:\n\n- A \"Not Submitted\" box denotes a beatmap that has not been uploaded to the osu! site using the Beatmap Submission System or was deleted by the mapper.\n- An \"Update to latest version\" box appears if there is a new version of the beatmap available for download. Click on the button to update.\n - **Note:** Once you update the beatmap, it cannot be reversed. If you want to preserve the older version for some reason (say, to keep scores), then do not update.\n- A \"Latest pending version\" box appears means that the beatmap has been uploaded to the osu!website but is not ranked yet.\n- If replays matching the view setting of the beatmap exist, they will be displayed instead of a box denoting the ranked/played status of the beatmap. This is shown in the above picture.\n - Under public rankings (e.g. Global, Friends, etc.), your high score will be shown at the bottom, as well as your rank on the leaderboard.\n- A \"No records set!\" box means that there are no replays for the current view setting (this is typically seen in the Local view setting if you just downloaded or edited the beatmap).\n - Note: Scores for Multi are not counted as records.\n\nThese are the view settings:\n\n- Local Ranking\n- Country Ranking\\*\n- Global Ranking\n- Global Ranking (Selected Mods)\\*\n- Friend Ranking\\*\n\n\\*Requires you to be an [osu!supporter](/wiki/osu!supporter) to access them.\n\nClick the word bubble icon to call up the **Quick Web Access** screen for the selected beatmap:\n\n- Press `1` or click the `Beatmap Listing/Scores` button and your default internet browser will pull up the Beatmap Listing and score page of the beatmap set the selected beatmap belongs to.\n- Press `2` or click `Beatmap Modding` and your default internet browser will pull up the modding page of the beatmap set the selected beatmap belongs to.\n- Press `3` or `Esc` or click `Cancel` to return to the Song Selection Screen.\n\nWhile you are on the Quick Web Access Screen, you cannot access the Chat and Extended Chat Consoles.\n\n### Song\n\n![](img/beatmap-cards.jpg)\n\nThe song list displays all available beatmaps. Different beatmaps may have different coloured boxes:\n\n- **Pink**: This beatmap has not been played yet.\n- **Orange**: At least one beatmap from the beatmapset has been completed.\n- **Light Blue**: Other beatmaps in the same set, shown when a mapset is expanded.\n- **White**: Currently selected beatmap.\n\nYou can navigate the beatmap list by using the mouse wheel, using the up and down arrow keys, dragging it while holding the left mouse button or clicking the right mouse button (previously known as Absolute Scrolling), which will move the scroll bar to your mouse's Y position. Click on a box to select that beatmap and display its information on the upper left, high scores (if any) on the left and, if you've cleared it, the letter grade of the highest score you've achieved. Click the box again, press `Enter` or click the osu!cookie at the lower right to begin playing the beatmap.\n\n### Gameplay toolbox\n\n![](img/game-mode-selector.jpg \"List of available game modes\")\n\n![](img/gameplay-toolbox.jpg)\n\nThis section can be called the gameplay toolbox. We will cover each button's use from left to right.\n\nPress `Esc` or click the `Back` button to return to main menu.\n\nClick on the `Mode` button to open up a list of gameplay modes available on osu!. Click on your desired gameplay mode and osu! will switch to that gameplay mode style - the scoreboard will change accordingly. Alternatively, you can press `Ctrl` and `1` (osu!), `2` (osu!taiko), `3` (osu!catch), or `4` (osu!mania) to change the gamemode.\n\nThe background transparent icon and the \"Mode\" box will change to depict what mode is currently selected.\n\n![](img/game-modifiers.jpg \"Mod Selection Screen\")\n\nClick the `Mods` button or press `F1` to open the **[Mod Selection Screen](/wiki/Game_modifier)**.\n\nIn this screen, you can apply modifications (\"mods\" for short) to gameplay. Some mods lower difficulty and apply a multiplier that lowers the score you achieve. Conversely, some mods increase the difficulty, but apply a multiplier that increases the score you achieve. Finally, some mods modify gameplay in a different way. [Relax](/wiki/Game_modifier/Relax) and [Auto Pilot](/wiki/Game_modifier/Autopilot) fall in that category.\n\nPlace your mouse on a mod's icon to see a short description of its effect. Click on an icon to select or deselect that mod. Some mods, like Double Time, have multiple variations; click on the mod again to cycle through. The score multiplier value displays the combined effect the multipliers of the mod(s) of you have selected will have on your score. Click `Reset all mods` or press `1` to deselect all currently selected mods. Click `Close` or press `2` or `Esc` to return to the Song Selection Screen.\n\nWhile you are on the Mod Selection Screen, you cannot access the Chat and Extended Chat Consoles. In addition, skins can alter the text and/or icon of the mods, but the effects will still be the same.\n\nClick the `Random` button or press `F2` to have the game **randomly scroll through all of your beatmaps and pick one.** You cannot select a beatmap yourself until it has finished scrolling.\n\n*Note: You can press `Shift` + the `Random` button or `F2` to go back to the beatmap you had selected before you randomized your selection.*\n\n![](img/beatmap-options.jpg \"Possible commands for a beatmap\")\n\nClick the `Beatmap Options` button, press `F3` or right-click your mouse while hovering over the beatmap to call up the **Beatmap Options Menu for options on the currently selected beatmap**.\n\n- Press `1` or click the `Manage Collections` button to bring up the Collections screen - here, you can manage pre-existing collections, as well as add or remove the currently selected beatmap or mapset to or from a collection.\n- Press `2` or click `Delete...` to delete the \\[1\\] currently selected beatmapset, \\[2\\] delete the currently selected beatmap, or \\[3\\] delete **all VISIBLE beatmaps**.\n - Note that deleted beatmaps are moved to the Recycle Bin.\n- Press `3` or click `Remove from Unplayed` to mark an unplayed beatmap as played (that is, change its box colour from pink to orange).\n- Press `4` or click `Clear local scores` to delete all records of the scores you have achieved in this beatmap.\n- Press `5` or click `Edit` to open the selected beatmap in osu!'s Editor.\n- Press `6` or `Esc` or click `Close` to return to the Song Selection Screen.\n\nClick on **your user panel** to access the **User Options Menu**.\n\nClick the **[osu!cookie](/wiki/Glossary#cookie)** to **start playing the selected beatmap**.\n\n## Results screen\n\n![](img/results-osu.jpg \"Accuracy in osu!\")\n\nThis is the results screen shown after you have successfully passed the beatmap. You can access your online results by scrolling down or pressing the obvious button.\n\n**Note:** The results screen may change depending on the used skin.\n\nBelow are the results screens of the other game modes.\n\n![](img/results-taiko.jpg \"Accuracy in osu!taiko\")\n\n![](img/results-mania.jpg \"Accuracy in osu!mania\")\n\n![](img/results-catch.jpg \"Accuracy in osu!catch\")\n\n### Online Leaderboard\n\n![](img/extended-results-screen.jpg \"An example of an osu!online score\")\n\nThis is your online leaderboard. You can go here by scrolling down from the results screen. Your Local Scoreboard will show your name and the score as usual.\n\n1. Your player bar. It shows your [PP](/wiki/Performance_Points), Global Rank, Total Score, Overall [Accuracy](/wiki/Accuracy), and level bar.\n2. `Save replay to Replays folder`: You can watch the replay later either by opening it from a local leaderboard, or by going to `Replays` directory and double clicking it.\n3. `Add as online favourite`: Include the beatmap into your list of favourites, which is located on your osu! profile page under the \"Beatmaps\" section.\n4. Local Leaderboard: All your results are stored on your computer. To see them, navigate to the [song selection screen](#song-selection-screen), then select `Local Rankings` from the drop-down menu on the left.\n5. `Beatmap Ranking` section. Available only for maps with online leaderboards ([qualified](/wiki/Beatmap/Category#qualified), [ranked](/wiki/Beatmap/Category#ranked), or [loved](/wiki/Beatmap/Category#loved)). You also need to be online to see this section.\n 1. `Overall`: Your position on the map's leaderboard, where you compete against players that used [mods](/wiki/Game_modifier), even if you didn't use any yourself.\n 2. `Accuracy`: How [precisely](/wiki/Accuracy) did you play the beatmap. Will only be counted when your old score is surpassed.\n 3. `Max Combo`: Your longest combo on the map you played.\n 4. `Ranked Score`: Your [best result](/wiki/Score#ranked-score) on the beatmap.\n 5. `Total Score`: Not taken into account, since it does not affect your position in online rankings.\n 6. `Performance`: The amount of [unweighted PP](/wiki/Performance_points#why-didnt-i-gain-the-full-amount-of-pp-from-a-map-i-played) you would receive for the play.\n6. `Overall Ranking` section. It's available only for beatmaps with online leaderboards. You also need to be online to see this section.\n 1. `Overall`: Your global ranking in the world.\n 2. `Accuracy`: Your average [accuracy](/wiki/Accuracy#accuracy) over all beatmaps you have played.\n 3. `Max Combo`: The longest combo over all beatmaps you have played.\n 4. [`Ranked Score`](/wiki/Score#ranked-score): The number of points earned from all ranked beatmaps that you have ever played, with every map being counted exactly once.\n 5. [`Total Score`](/wiki/Score#total-score): Same as ranked score, but it takes into account all beatmaps available on the osu! website, and also underplayed or failed beatmaps. This counts towards your level.\n 6. `Perfomance`: Displays your total amount of Performance Points, and also how many PP the submitted play was worth.\n7. Information about the beatmap with its playcount and pass rate.\n8. Beatmap rating. Use your personal discretion based on whether you enjoy the beatmap or not. Best left alone if you can't decide.\n9. Click here to return to the song selection screen.\n\n![](img/medal-unlock.jpg \"Unlocking a medal\")\n\nAbove is what it looks like to receive a medal.\n", + "# Formatting\n\n*For the writing standards, see: [Article style criteria/Writing](../Writing)*\n\n*Notice: This article uses [RFC 2119](https://tools.ietf.org/html/rfc2119 \"IETF Tools\") to describe requirement levels.*\n\n## Locales\n\nListed below are the properly-supported locales for the wiki:\n\n| File Name | Locale Name | Native Script |\n| :-- | :-- | :-- |\n| `en.md` | English | English |\n| `ar.md` | Arabic | اَلْعَرَبِيَّةُ |\n| `be.md` | Belarusian | Беларуская мова |\n| `bg.md` | Bulgarian | Български |\n| `cs.md` | Czech | Česky |\n| `da.md` | Danish | Dansk |\n| `de.md` | German | Deutsch |\n| `gr.md` | Greek | Ελληνικά |\n| `es.md` | Spanish | Español |\n| `fi.md` | Finnish | Suomi |\n| `fr.md` | French | Français |\n| `hu.md` | Hungarian | Magyar |\n| `id.md` | Indonesian | Bahasa Indonesia |\n| `it.md` | Italian | Italiano |\n| `ja.md` | Japanese | 日本語 |\n| `ko.md` | Korean | 한국어 |\n| `nl.md` | Dutch | Nederlands |\n| `no.md` | Norwegian | Norsk |\n| `pl.md` | Polish | Polski |\n| `pt.md` | Portuguese | Português |\n| `pt-br.md` | Brazilian Portuguese | Português (Brasil) |\n| `ro.md` | Romanian | Română |\n| `ru.md` | Russian | Русский |\n| `sk.md` | Slovak | Slovenčina |\n| `sv.md` | Swedish | Svenska |\n| `th.md` | Thai | ไทย |\n| `tr.md` | Turkish | Türkçe |\n| `uk.md` | Ukrainian | Українська мова |\n| `vi.md` | Vietnamese | Tiếng Việt |\n| `zh.md` | Chinese (Simplified) | 简体中文 |\n| `zh-tw.md` | Traditional Chinese (Taiwan) | 繁體中文(台灣) |\n\n*Note: The website will give readers their selected language's version of an article. If it is not available, the English version will be given.*\n\n### Content parity\n\nTranslations are subject to strict content parity with their English article, in the sense that they must have the same message, regardless of grammar and syntax. Any changes to the translations' meanings must be accompanied by equivalent changes to the English article.\n\nThere are some cases where the content is allowed to differ:\n\n- Articles originally written in a language other than English (in this case, English should act as the translation)\n- Explanations of English words that are common terms in the osu! community\n- External links\n- Tags\n- Subcommunity-specific explanations\n\n## Front matter\n\nFront matter must be placed at the very top of the file. It is written in [YAML](https://en.wikipedia.org/wiki/YAML#Example \"YAML Wikipedia article\") and describes additional information about the article. This must be surrounded by three hyphens (`---`) on the lines above and below it, and an empty line must follow it before the title heading.\n\n### Articles that need help\n\n*Note: Avoid translating English articles with this tag. In addition to this, this tag should be added when the translation needs its own clean up.*\n\nThe `needs_cleanup` tag may be added to articles that need rewriting or formatting help. It is also acceptable to open an issue on GitHub for this purpose. This tag must be written as shown below:\n\n```yaml\nneeds_cleanup: true\n```\n\nWhen adding this tag to an article, [comments](#comments) should also be added to explain what needs to be done to remove the tag.\n\n### Outdated articles\n\n*Note: Avoid translating English articles with this tag. If the English article has this tag, the translation must also have this tag.*\n\nTranslated articles that are outdated must use the `outdated` tag when the English variant is updated. English articles may also become outdated when the content they contain is misleading or no longer relevant. This tag must be written as shown below:\n\n```yaml\noutdated: true\n```\n\nWhen adding this tag to an article, [comments](#comments) should also be added to explain what needs to be updated to remove the tag.\n\n### Tagging articles\n\nTags help the website's search engine query articles better. Tags should be written in the same language as the article and include the original list of tags. Tags should use lowercase letters where applicable.\n\nFor example, an article called \"Beatmap discussion\" may include the following tags:\n\n```yaml\ntags:\n - beatmap discussions\n - modding V2\n - MV2\n```\n\n### Translations without reviews\n\n*Note: Wiki maintainers will determine and apply this mark prior to merging.*\n\nSometimes, translations are added to the wiki without review from other native speakers of the language. In this case, the `no_native_review` mark is added to let future translators know that it may need to be checked again. This tag must be written as shown below:\n\n```yaml\nno_native_review: true\n```\n\n## Article naming\n\n*See also: [Folder names](#folder-names) and [Titles](#titles)*\n\nArticle titles should be singular and use sentence case. See [Wikipedia's naming conventions article](https://en.wikipedia.org/wiki/Wikipedia:Naming_conventions_(plurals) \"Wikipedia\") for more details.\n\nArticle titles should match the folder name it is in (spaces may replace underscores (`_`) where appropriate). If the folder name changes, the article title should be changed to match it and vice versa.\n\n---\n\nContest and tournament articles are an exception. The folder name must use abbreviations, acronyms, or initialisms. The article's title must be the full name of the contest or tournament.\n\n## Folder and file structure\n\n### Folder names\n\n*See also: [Article naming](#article-naming)*\n\nFolder names must be in English and use sentence case.\n\nFolder names must only use these characters:\n\n- uppercase and lowercase letters\n- numbers\n- underscores (`_`)\n- hyphens (`-`)\n- exclamation marks (`!`)\n\n### Article file names\n\nThe file name of an article can be found in the `File Name` column of the [locales section](#locales). The location of a translated article must be placed in the same folder as the English article.\n\n### Index articles\n\nAn index article must be created if the folder is intended to only hold other articles. Index articles must contain a list of articles that are inside its own folder. They may also contain other information, such as a lead paragraph or descriptions of the linked articles.\n\n### Disambiguation articles\n\n[Disambiguation](/wiki/Disambiguation) articles must be placed in the `/wiki/Disambiguation` folder. The main page must be updated to include the disambiguation article. Refer to [Disambiguation/Mod](/wiki/Disambiguation/Mod) as an example.\n\nRedirects must be updated to have the ambiguous keyword(s) redirect to the disambiguation article.\n\nArticles linked from a disambiguation article must have a [For other uses](#for-other-uses) hatnote.\n\n## HTML\n\nHTML must not be used, with exception for [comments](#comments). The structure of the article must be redone if HTML is used.\n\n### Comments\n\nHTML comments should be used for marking to-dos, but may also be used to annotate text. They should be on their own line, but can be placed inline in a paragraph. If placed inline, the start of the comment must not have a space.\n\nBad example:\n\n```markdown\nHTML comments should be used for marking to-dos or annotate text.\n```\n\nGood example:\n\n```markdown\nHTML comments should be used for marking to-dos or annotate text.\n```\n\n## Editing\n\n### End of line sequence\n\n*Caution: Uploading Markdown files using `CRLF` (carriage return and line feed) via GitHub will result in those files using `CRLF`. To prevent this, set the line ending to `LF` (line feed) before uploading.*\n\nMarkdown files must be checked in using the `LF` end of line sequence.\n\n### Escaping\n\nMarkdown syntax should be escaped as needed. However, article titles are parsed as plain text and so must not be escaped.\n\n### Paragraphs\n\nEach paragraph must be followed by one empty line.\n\n### Line breaks\n\nLine breaks must use a backslash (`\\`).\n\nLine breaks must be used sparingly.\n\n## Hatnote\n\n*Not to be confused with [Notice](#notice).*\n\nHatnotes are short notes placed at the top of an article or section to help readers navigate to related articles or inform them about related topics.\n\nHatnotes must be italicised and be placed immediately after the heading. If multiple hatnotes are used, they must be on the same paragraph separated with a line break.\n\n### Main page\n\n*Main page* hatnotes direct the reader to the main article of a topic. When this hatnote is used, it implies that the section it is on is a summary of what the linked page is about. This hatnote should have only one link. These must be formatted as follows:\n\n```markdown\n*Main page: {article}*\n\n*Main pages: {article} and {article}*\n```\n\n### See also\n\n*See also* hatnotes suggest to readers other points of interest from a given article or section. These must be formatted as follows:\n\n```markdown\n*See also: {article}*\n\n*See also: {article} and {article}*\n```\n\n### For see\n\n*For see* hatnotes are similar to *see also* hatnotes, but are generally more descriptive and direct. This hatnote may use more than one link if necessary. These must be formatted as follows:\n\n```markdown\n*For {description}, see: {article}`*\n\n*For {description}, see: {article} and {article}`*\n```\n\n### Not to be confused with\n\n*Not to be confused with* hatnotes help distinguish ambiguous or misunderstood article titles or sections. This hatnote may use more than one link if necessary. These must be formatted as follows:\n\n```markdown\n*Not to be confused with {article}.*\n\n*Not to be confused with {article} or {article}.*\n```\n\n### For other uses\n\n*For other uses* hatnotes are similar to *not to be confused with* hatnotes, but links directly to the [disambiguation article](#disambiguation-articles). This hatnote must only link to the disambiguation article. These must be formatted as follows:\n\n```markdown\n*For other uses, see {disambiguation article}.*\n```\n\n## Notice\n\n*Not to be confused with [Hatnote](#hatnote).*\n\nA notice should be placed where appropriate in a section, but must start off the paragraph and use italics. Notices may contain bolding where appropriate, but should be kept to a minimum. Notices must be written as complete sentences. Thus, unlike most [hatnotes](#hatnotes), must use a full stop (`.`) or an exclamation mark (`!`) if appropriate. Anything within the same paragraph of a notice must also be italicised. These must be formatted as follows:\n\n```markdown\n*Note: {note}.*\n\n*Notice: {notice}.*\n\n*Caution: {caution}.*\n\n*Warning: {warning}.*\n```\n\n- `Note` should be used for factual or trivial details.\n- `Notice` should be used for reminders or to draw attention to something that the reader should be made aware of.\n- `Caution` should be used to warn the reader to avoid unintended consequences.\n- `Warning` should be used to warn the reader that action may be taken against them.\n\n## Emphasising\n\n### Bold\n\nBold must use double asterisks (`**`).\n\nLead paragraphs may bold the first occurrence of the article's title.\n\n### Italics\n\nItalics must use single asterisks (`*`).\n\nNames of work or video games should be italicised. osu!—the game—is exempt from this.\n\nThe first occurrence of an abbreviation, acronym, or initialism may be italicised.\n\nItalics may also be used to provide emphasis or help with readability.\n\n## Headings\n\nAll headings must use sentence case.\n\nHeadings must use the [ATX (hash) style](https://github.github.com/gfm/#atx-headings \"GitHub\") and must have an empty line before and after the heading. The title heading is an exception when it is on the first line. If this is the case, there only needs to be an empty line after the title heading.\n\nHeadings must not exceed a heading level of 5 and must not be used to style or format text.\n\n### Titles\n\n*See also: [Article naming](#article-naming)*\n\n*Caution: Titles are parsed as plain text; they must not be escaped.*\n\nThe first heading in all articles must be a level 1 heading, being the article's title. All headings afterwards must be [section headings](#sections). Titles must not contain formatting, links, or images.\n\nThe title heading must be on the first line, unless [front matter](#front-matter) is being used. If that is the case, the title heading must go after it and have an empty line before the title heading.\n\n### Sections\n\nSection headings must use levels 2 to 5. The section heading proceeding the [title heading](#titles) must be a level 2 heading. Unlike titles, section headings may have small image icons.\n\nSection headings must not skip a heading level (i.e. do not go from a level 2 heading to a level 4 heading) and must not contain formatting or links.\n\n*Notice: On the website, heading levels 4 and 5 will not appear in the table of contents. They cannot be linked to directly either.*\n\n## Lists\n\nLists should not go over 4 levels of indentation and should not have an empty line in between each item.\n\nFor nested lists, bullets or numbers must align with the item content of their parent lists.\n\nThe following example was done incorrectly (take note of the spacing before the bullet):\n\n```markdown\n1. Fly a kite\n - Don't fly a kite if it's raining\n```\n\nThe following example was done correctly:\n\n```markdown\n1. Fly a kite\n - Don't fly a kite if it's raining\n```\n\n### Bulleted\n\nBulleted lists must use a hyphen (`-`). These must then be followed by one space. (Example shown below.)\n\n```markdown\n- osu!\n - Hit circle\n - Combo number\n - Approach circle\n - Slider\n - Hit circles\n - Slider body\n - Slider ticks\n - Spinner\n- osu!taiko\n```\n\n### Numbered\n\nThe numbers in a numbered list must be incremented to represent their step.\n\n```markdown\n1. Download the osu! installer.\n2. Run the installer.\n 1. To change the installation location, click the text underneath the progression bar.\n 2. The installer will prompt for a new location, choose the installation folder.\n3. osu! will start up once installation is complete.\n4. Sign in.\n```\n\n### Mixed\n\nCombining both bulleted and numbered lists should be done sparingly.\n\n```markdown\n1. Download a skin from the forums.\n2. Load the skin file into osu!.\n - If the file is a `.zip`, unzip it and move the contents into the `Skins/` folder (found in your osu! installation folder).\n - If the file is a `.osk`, open it on your desktop or drag-and-drop it into the game client.\n3. Open osu!, if it is not opened, and select the skin in the options.\n - This may have been completed if you opened the `.osk` file or drag-and-dropped it into the game client.\n```\n\n## Code\n\nThe markup for code is a grave mark (`` ` ``). To put grave marks in code, use double grave marks instead. If the grave mark is at the start or end, pad it with one space. (Example shown below.)\n\n```markdown\n`` ` ``\n`` `Space` ``\n```\n\n### Keyboard keys\n\n*Notice: When denoting the letter itself, and not the keyboard key, use quotation marks instead.*\n\nWhen representing keyboard keys, use capital letters for single characters and title case for modifiers. Use the plus symbol (`+`) (without code) to represent key combinations. (Example shown below.)\n\n```markdown\npippi is spelt with a lowercase \"p\" like peppy.\n\nPress `Ctrl` + `O` to open the open dialog.\n```\n\nWhen representing a space or the spacebar, use `` `Space` ``.\n\n### Button and menu text\n\nWhen copying the text from a menu or button, the letter casing should be copied as it appears. (Example shown below.)\n\n```markdown\nThe `osu!direct` button is visible in the main menu on the right side, if you have an active osu!supporter tag.\n```\n\n### Folder and directory names\n\nWhen copying the name of a folder or directory, the letter casing should be copied as it appears, but prefer lowercased paths when possible. Directory paths must not be absolute (i.e. do not start the directory name from the drive letter or from the root folder). (Example shown below.)\n\n```markdown\nosu! is installed in the `AppData/Local` folder by default, unless specified otherwise during installation.\n```\n\n### Keywords and commands\n\nWhen copying a keyword or command, the letter casing should be copied as it appears or how someone normally would type it. If applicable, prefer lowercase letters. (Example shown below.)\n\n```markdown\nAs of now, the `Name` and `Author` commands in the skin configuration file (`skin.ini`) do nothing.\n```\n\n### File names\n\nWhen copying the name of a file, the letter casing should be copied as it appears. If applicable, prefer lowercase letters. (Example shown below.)\n\n```markdown\nTo play osu!, double click the `osu!.exe` icon.\n```\n\n### File extensions\n\n*Notice: File formats (not to be confused with file extensions) must be written in capital letters without the prefixed fullstop (`.`).*\n\nFile extensions must be prefixed with a fullstop (`.`) and be followed by the file extension in lowercase letters. (Example shown below.)\n\n```markdown\nThe JPG (or JPEG) file format has the `.jpg` (or `.jpeg`) extension.\n```\n\n### Chat channels\n\nWhen copying the name of a chat channel, start it with a hash (`#`), followed by the channel name in lowercase letters. (Example shown below.)\n\n```markdown\n`#lobby` is where you can advertise your multi room.\n```\n\n## Preformatted text (code blocks)\n\n*Notice: Syntax highlighting for preformatted text is not implemented on the website yet.*\n\nPreformatted text (also known as code blocks) must be fenced using three grave marks. They should set the language identifier for syntax highlighting.\n\n## Links\n\nThere are two types of links: inline and reference. Inline has two styles.\n\nThe following is an example of both inline styles:\n\n```markdown\n[Game Modifiers](/wiki/Game_Modifiers)\n\n\n```\n\nThe following is an example of the reference style:\n\n```markdown\n[Game Modifiers][game mods link]\n\n[game mods link]: /wiki/Game_Modifiers\n```\n\n---\n\nLinks must use the inline style if they are only referenced once. The inline angle brackets style should be avoided. References to reference links must be placed at the bottom of the article.\n\n### Internal links\n\n*Note: Internal links refer to links that stay inside the `https://osu.ppy.sh/` domain.*\n\n#### Wiki links\n\nAll links that point to an wiki article should start with `/wiki/` followed by the path to get to the article you are targeting. Relative links may also be used. Some examples include the following:\n\n```markdown\n[FAQ](/wiki/FAQ)\n[pippi](/wiki/Mascots#-pippi)\n[Beatmaps](../)\n[Pattern](./Pattern)\n```\n\nWiki links must not use redirects and must not have a trailing forward slash (`/`).\n\nBad examples include the following:\n\n```markdown\n[Article styling criteria](/wiki/ASC)\n[Developers](/wiki/Developers/)\n[Developers](/wiki/Developers/#game-client-developers)\n```\n\nGood examples include the following:\n\n```markdown\n[Article styling criteria](/wiki/Article_styling_criteria)\n[Developers](/wiki/Developers)\n[Developers](/wiki/Developers#game-client-developers)\n```\n\n##### Sub-article links\n\nWiki links that point to a sub-article should include the parent article's folder name in its link text. See the following example:\n\n```markdown\n*See also: [Beatmap Editor/Design](/wiki/Beatmap_Editor/Design)*\n```\n\n##### Section links\n\n*Notice: On the website, heading levels 4 and 5 are not given the id attribute. This means that they can not be linked to directly.*\n\nWiki links that point to a section of an article may use the section sign symbol (`§`). See the following example:\n\n```markdown\n*For timing rules, see: [Ranking Criteria § Timing](/wiki/Ranking_Criteria#timing)*\n```\n\n#### Other osu! links\n\nThe URL from the address bar of your web browser should be copied as it is when linking to other osu! web pages. The `https://osu.ppy.sh` part of the URL must be kept.\n\n##### User profiles\n\nAll usernames must be linked on first occurrence. Other occurrences are optional, but must be consistent throughout the entire article for all usernames. If it is difficult to determine the user's id, it may be skipped over.\n\nWhen linking to a user profile, the user's id number must be used. Use the new website (`https://osu.ppy.sh/users/{username})`) to get the user's id.\n\nThe link text of the user link should be the user's current name.\n\n##### Difficulties\n\nWhenever linking to a single difficulty, use this format as the link text:\n\n```\n{artist} - {title} ({creator}) [{difficuty_name}]\n```\n\nThe link must actually link to that difficulty. Beatmap difficulty URLs must be formatted as follows:\n\n```\nhttps://osu.ppy.sh/beatmapsets/{BeatmapSetID}#{mode}/{BeatmapID}\n```\n\nThe difficulty name may be left outside of the link text, but doing so must be consistent throughout the entire article.\n\n##### Beatmaps\n\nWhenever linking to a beatmap, use this format as the link text:\n\n```\n{artist} - {title} ({creator})\n```\n\nAll beatmap URLs must be formatted as follows:\n\n```\nhttps://osu.ppy.sh/beatmapsets/{BeatmapSetID}\n```\n\n### External links\n\n*Notice: External links refers to links that go outside the `https://osu.ppy.sh/` domain.*\n\nThe `https` protocol must be used, unless the site does not support it. External links must be a clean and direct link to a reputable source. The link text should be the title of the page it is linking to. The URL from the address bar of your web browser should be copied as it is when linking to other external pages.\n\nThere are no visual differences between external and osu! web links. Due to this, the website name should be included in the title text. See the following example:\n\n```markdown\n*For more information about music theory, see: [Music theory](https://en.wikipedia.org/wiki/Music_theory \"Wikipedia\")*\n```\n\n## Images\n\nThere are two types of image links: inline and reference. Examples:\n\n**Inline style:**\n\n```markdown\n![](/wiki/shared/flag/AU.gif)\n```\n\n**Reference style:**\n\n```markdown\n![][flag_AU]\n\n[flag_AU]: /wiki/shared/flag/AU.gif\n```\n\nImages should use the inline linking style. References to reference links must be placed at the bottom of the article.\n\nImages must be placed in a folder named `img`, located in the article's folder. Images that are used in multiple articles should be stored in the `/wiki/shared/` folder.\n\n### Image caching\n\nImages on the website are cached for up to 60 days. The cached image is matched with the image link's URL.\n\nWhen updating an image, either change the image's name or append a query string to the URL. In both cases, all translations linking to the updated image should also be updated.\n\n### Formats and quality\n\nImages should use the JPG format at quality 8 (80 or 80%, depending on the program). If the image contains transparency or has text that must be readable, use the PNG format instead. If the image contains an animation, the GIF format can be used; however, this should be used sparingly as these may take longer to load or can be bigger then the [max file size](#file-size).\n\n### File size\n\nImages must be under 1 megabyte, otherwise they will fail to load. Downscaling and using JPG at 80% is almost always under the size limit.\n\nAll images should be optimised as much as possible. Use [jpeg-archive](https://github.com/danielgtaylor/jpeg-archive \"GitHub\") to compress JPEG images. For consistency, use the following command for jpeg-archive:\n\n```sh\njpeg-recompress -am smallfry \n```\n\nWhere `` is the file name to be compressed and `` is the compressed file name.\n\n### File names\n\n*Notice: File extensions must use lowercase letters, otherwise they will fail to load!*\n\nUse hyphens (`-`) when spacing words. When naming an image, the file name should be meaningful or descriptive but short.\n\n### Formatting and positioning\n\n*Note: It is currently not possible to float an image or have text wrap around it.*\n\nImages on the website will be centred when it is on a single line, by themself. Otherwise, they will be positioned inline with the paragraph. The following example will place the image in the center:\n\n```markdown\nInstalling osu! is easy. First, download the installer from the download page.\n\n![](img/download-page.jpg)\n\nThen locate the installer and run it.\n```\n\n### Alt text\n\nImages should have alt text unless it is for decorative purposes.\n\n### Captions\n\nImages are given captions on the website if they fulfill these conditions:\n\n1. The image is by itself.\n2. The image is not inside a heading.\n3. The image has title text.\n\nCaptions are assumed via the title text, which must be in plain text. Images with captions are also centred with the image on the website.\n\n### Max image width\n\nThe website's max image width is the width of the article body. Images should be no wider than 800 pixels.\n\n### Annotating images\n\nWhen annotating images, use *Torus Regular*. For Chinese, Korean, Japanese characters, use *Microsoft YaHei*.\n\nAnnotating images should be avoided, as it is difficult for translators (and other editors) to edit them.\n\n#### Translating annotated images\n\nWhen translating annotated images, the localised image version must be placed in the same directory as the original version (i.e. the English version). The filename of a localised image version must start with the original version's name, followed by a hyphen, followed by the locale name (in capital letters). See the following examples:\n\n- `hardrock-mod-vs-easy-mod.jpg` for English\n- `hardrock-mod-vs-easy-mod-DE.jpg` for German\n- `hardrock-mod-vs-easy-mod-ZH-TW.jpg` for Traditional Chinese\n\n### Screenshots of gameplay\n\nAll screenshots of gameplay must be done in the stable build, unless it is for a specific feature that is unavailable in the stable build. You should use the in-game screenshot feature (`F12`).\n\n#### Game client settings\n\n*Note: If you do not want to change your current settings for the wiki, you can move your `osu!..cfg` out of the osu! folder and move it back later.*\n\nYou must set these settings before taking a screenshot of the game client (settings not stated below are assumed to be at their defaults):\n\n- Select language: `English`\n- Prefer metadata in original language: `Enabled`\n- Resolution: `1280x720`\n- Fullscreen mode: `Disabled`\n- Parallax: `Disabled`\n- Menu tips: `Disabled`\n- Seasonal backgrounds: `Never`\n- Always show key overlay: `Enabled`\n- Current skin: `Default` (first option)\n\n*Notice to translators: If you are translating an article containing screenshots of the game, you may set the game client's language to the language you are translating in.*\n\n### Image links\n\nImages must not be part of a link text.\n\nFlag icons next to user links must be separate from the link text. See the following example:\n\n```markdown\n![][flag_AU] [peppy](https://osu.ppy.sh/users/2)\n```\n\n### Flag icons\n\n*For a list of flag icons, see: [issue \\#328](https://github.com/ppy/osu-wiki/issues/328 \"GitHub\")*\n\nThe flag icons use the two letter code (in all capital letters) and end with `.gif`. When adding a flag inline, use this format:\n\n```markdown\n![](/wiki/shared/flag/xx.gif)\n```\n\nWhere `xx` is the [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 \"Wikipedia\") two-lettered country code for the flag.\n\nThe full country name should be added in the title text. The country code in the alternate text is optional, but must be applied to all flag icons in the article.\n\n## Tables\n\nTables on the website only support headings along the first row.\n\nTables must not be beautified (do not pad cells with extra spaces to make their widths uniform). They must have a vertical bar (`|`) on the left and right sides and the text of each cell must be padded with one space on both sides. Empty cells must use a vertical bar (`|`) followed by two spaces then another vertical bar (`|`).\n\nThe delimiter row (the next line after the table heading) must use only three characters per column (and be padded with a space on both sides), which must look like one of the following:\n\n- `:--` (for left align)\n- `:-:` (for centre align)\n- `--:` (for right align)\n\n---\n\nThe following is an example of what a table should look like:\n\n```markdown\n| Team \"Picturesque\" Red | Score | Team \"Statuesque\" Blue | Average Beatmap Stars |\n| :-- | :-: | --: | :-- |\n| **peppy** | 5 - 2 | pippi | 9.3 stars |\n| Aiko | 1 - 6 | **Alisa** | 4.2 stars |\n| Ryūta | 3 - 4 | **Yuzu** | 5.1 stars |\n| **Taikonator** | 7 - 0 | Tama | 13.37 stars |\n| Maria | No Contest | Mocha | |\n```\n\n## Blockquotes\n\nThe blockquote is limited to quoting text from someone. It must not be used to format text otherwise.\n\n## Thematic breaks\n\nThe thematic break (also known as the horizontal rule or line) should be used sparingly. A few uses of the thematic break may include (but is not limited to):\n\n- separating images from text\n- separating multiple images that follow one another\n- shifting the topic within a section\n\nThese must have an empty line before and after the markup. Thematic breaks must use only three hyphens, as depicted below:\n\n```markdown\n---\n```\n" }; } } From eebd5745a823378432472cdca31e3e06904c8109 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 11:44:43 +0900 Subject: [PATCH 0401/1621] Move full stop out of link and reword slightly --- osu.Game/Overlays/WikiOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 2261f71c04..c5ba0eed66 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -139,7 +139,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, CurrentPath = $@"{api.WebsiteRootUrl}/wiki/", - Text = $"Something went wrong when trying to fetch page \"{path.Value}\".\n\n[Go back to main page.](Main_Page)", + Text = $"Something went wrong when trying to fetch page \"{path.Value}\".\n\n[Return to the main page](Main_Page).", DocumentMargin = new MarginPadding(0), DocumentPadding = new MarginPadding { From 7e781501443b62d2b3dc4102b2375c35252c7f42 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 10:16:49 +0700 Subject: [PATCH 0402/1621] add basic content wiki sidebar --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index 6d1f520135..1f48d026ad 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -1,9 +1,36 @@ // 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.Graphics; +using osu.Game.Graphics.Sprites; + namespace osu.Game.Overlays.Wiki { public class WikiSidebar : OverlaySidebar { + private FillFlowContainer tableOfContents; + + protected override Drawable CreateContent() => new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "CONTENTS", + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + }, + tableOfContents = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + }, + }; } } From b1b305c150aeb2bb5c6df0a7d62a9f72e5fcba3e Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 10:17:38 +0700 Subject: [PATCH 0403/1621] add method AddToc --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index 1f48d026ad..0f4ae45650 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -32,5 +33,19 @@ namespace osu.Game.Overlays.Wiki } }, }; + + public void AddToc(string title, MarkdownHeading heading, int level) + { + switch (level) + { + case 2: + tableOfContents.Add(new OsuSpriteText + { + Text = title, + Font = OsuFont.GetFont(size: 15), + }); + break; + } + } } } From abb522f084fa882fbf28dd858df0c1dcb0d8ecc6 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 10:53:48 +0700 Subject: [PATCH 0404/1621] add missing using --- osu.Game/Overlays/WikiOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 4cfbbbbfe4..812f26e77d 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -11,6 +11,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Wiki; +using osu.Game.Overlays.Wiki.Markdown; namespace osu.Game.Overlays { From 59dbed64180576aca1f8a7eaec39bfb550ebeb91 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 11:08:51 +0700 Subject: [PATCH 0405/1621] create ArticleMarkdownContainer in WikiArticlePage --- osu.Game/Overlays/Wiki/WikiArticlePage.cs | 25 +++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiArticlePage.cs b/osu.Game/Overlays/Wiki/WikiArticlePage.cs index c41ab8b250..12a3ee386e 100644 --- a/osu.Game/Overlays/Wiki/WikiArticlePage.cs +++ b/osu.Game/Overlays/Wiki/WikiArticlePage.cs @@ -1,14 +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 System; +using Markdig.Syntax; +using Markdig.Syntax.Inlines; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; using osu.Game.Overlays.Wiki.Markdown; namespace osu.Game.Overlays.Wiki { public class WikiArticlePage : GridContainer { + private readonly WikiSidebar sidebar; public Container SidebarContainer { get; } public WikiArticlePage(string currentPath, string markdown) @@ -31,9 +36,9 @@ namespace osu.Game.Overlays.Wiki SidebarContainer = new Container { AutoSizeAxes = Axes.X, - Child = new WikiSidebar(), + Child = sidebar = new WikiSidebar(), }, - new WikiMarkdownContainer + new ArticleMarkdownContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -46,9 +51,25 @@ namespace osu.Game.Overlays.Wiki Left = 30, Right = 50, }, + OnAddHeading = sidebar.AddToc, } }, }; } + + private class ArticleMarkdownContainer : WikiMarkdownContainer + { + public Action OnAddHeading; + + protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) + { + var heading = base.CreateHeading(headingBlock); + var title = ((LiteralInline)headingBlock.Inline.FirstChild).Content.ToString(); + + OnAddHeading(title, heading, headingBlock.Level); + + return heading; + } + } } } From d8d4bf66b3fdfd7367f347325d8a03f44b841fd7 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 11:09:20 +0700 Subject: [PATCH 0406/1621] create TocTitle in WikiSidebar --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 34 +++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index 0f4ae45650..aa521d4ff5 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -1,10 +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.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Wiki @@ -39,13 +42,34 @@ namespace osu.Game.Overlays.Wiki switch (level) { case 2: - tableOfContents.Add(new OsuSpriteText - { - Text = title, - Font = OsuFont.GetFont(size: 15), - }); + tableOfContents.Add(new TocTitle(title)); break; } } + + private class TocTitle : OsuHoverContainer + { + private readonly OsuSpriteText spriteText; + + public TocTitle(string text) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Child = spriteText = new OsuSpriteText + { + Text = text, + Font = OsuFont.GetFont(size: 15), + }; + } + + protected override IEnumerable EffectTargets => new Drawable[] { spriteText }; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Light2; + HoverColour = colourProvider.Light1; + } + } } } From 41ec531bab6c54a331ded55d8d86070f90e3ca35 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 11:12:42 +0700 Subject: [PATCH 0407/1621] add subtitle toc --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index aa521d4ff5..c2e28f4a81 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -44,6 +44,10 @@ namespace osu.Game.Overlays.Wiki case 2: tableOfContents.Add(new TocTitle(title)); break; + + case 3: + tableOfContents.Add(new TocTitle(title, true)); + break; } } @@ -51,14 +55,14 @@ namespace osu.Game.Overlays.Wiki { private readonly OsuSpriteText spriteText; - public TocTitle(string text) + public TocTitle(string text, bool subtitle = false) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Child = spriteText = new OsuSpriteText { Text = text, - Font = OsuFont.GetFont(size: 15), + Font = OsuFont.GetFont(size: subtitle ? 12 : 15), }; } From 424d1b402587256e5c872ac38ea91e20262eacbb Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 11:18:08 +0700 Subject: [PATCH 0408/1621] add margin padding spacing in toc title --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index c2e28f4a81..e25e6189d7 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -64,6 +64,8 @@ namespace osu.Game.Overlays.Wiki Text = text, Font = OsuFont.GetFont(size: subtitle ? 12 : 15), }; + Margin = new MarginPadding { Top = subtitle ? 5 : 10 }; + Padding = new MarginPadding { Left = subtitle ? 10 : 0 }; } protected override IEnumerable EffectTargets => new Drawable[] { spriteText }; From 4e73d0254044741d8a34194dac3589566879c856 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 11:35:23 +0700 Subject: [PATCH 0409/1621] move sidebar into local variable --- osu.Game/Overlays/Wiki/WikiArticlePage.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Wiki/WikiArticlePage.cs b/osu.Game/Overlays/Wiki/WikiArticlePage.cs index 12a3ee386e..99068d6919 100644 --- a/osu.Game/Overlays/Wiki/WikiArticlePage.cs +++ b/osu.Game/Overlays/Wiki/WikiArticlePage.cs @@ -13,7 +13,6 @@ namespace osu.Game.Overlays.Wiki { public class WikiArticlePage : GridContainer { - private readonly WikiSidebar sidebar; public Container SidebarContainer { get; } public WikiArticlePage(string currentPath, string markdown) @@ -29,6 +28,9 @@ namespace osu.Game.Overlays.Wiki new Dimension(GridSizeMode.AutoSize), new Dimension(), }; + + WikiSidebar sidebar; + Content = new[] { new Drawable[] From e28b38653be073deae56b4cb2e44f31f0e101a6a Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 11:43:00 +0700 Subject: [PATCH 0410/1621] cache scroll container --- osu.Game/Overlays/WikiOverlay.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 812f26e77d..5beb5d50e6 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -26,6 +26,9 @@ namespace osu.Game.Overlays [Resolved] private IAPIProvider api { get; set; } + [Cached] + private readonly OverlayScrollContainer scrollContainer; + private GetWikiRequest request; private CancellationTokenSource cancellationToken; @@ -37,6 +40,7 @@ namespace osu.Game.Overlays public WikiOverlay() : base(OverlayColourScheme.Orange, false) { + scrollContainer = ScrollFlow; } public void ShowPage(string pagePath = index_path) From 37ff6299c9c73df7c2eb374e3ab0acac57776e1d Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 11:43:32 +0700 Subject: [PATCH 0411/1621] add target in toc title --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index e25e6189d7..b3e315fefc 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -42,21 +42,25 @@ namespace osu.Game.Overlays.Wiki switch (level) { case 2: - tableOfContents.Add(new TocTitle(title)); - break; - case 3: - tableOfContents.Add(new TocTitle(title, true)); + tableOfContents.Add(new TocTitle(title, heading, level == 3)); break; } } private class TocTitle : OsuHoverContainer { + [Resolved] + private OverlayScrollContainer scrollContainer { get; set; } + private readonly OsuSpriteText spriteText; - public TocTitle(string text, bool subtitle = false) + private readonly MarkdownHeading target; + + public TocTitle(string text, MarkdownHeading target, bool subtitle = false) { + this.target = target; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Child = spriteText = new OsuSpriteText From 91e77ee4dea2649d541ad9d305ba40b53aa6b922 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 11:43:49 +0700 Subject: [PATCH 0412/1621] add onclick in toc title --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index b3e315fefc..1c5e11a7ca 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -80,6 +81,12 @@ namespace osu.Game.Overlays.Wiki IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; } + + protected override bool OnClick(ClickEvent e) + { + scrollContainer.ScrollTo(target); + return base.OnClick(e); + } } } } From 59536747373395b413d4fa79a43184a4ef0391ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 14:06:08 +0900 Subject: [PATCH 0413/1621] Tidy up constants --- osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs | 1 - osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs index 0b43704439..cb68d4b7a7 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs @@ -45,7 +45,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default new KiaiFlash { RelativeSizeAxes = Axes.Both, - FlashOpacity = 0.25f, }, triangles = new TrianglesPiece { diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs index a13bcba4c7..d49b1713f6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs @@ -12,7 +12,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public class KiaiFlash : BeatSyncedContainer { - public float FlashOpacity = 1f; + private const double fade_length = 80; + + private const float flash_opacity = 0.25f; public KiaiFlash() { @@ -30,14 +32,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { if (!effectPoint.KiaiMode) - { return; - } Child - .FadeTo(FlashOpacity, EarlyActivationMilliseconds, Easing.OutQuint) + .FadeTo(flash_opacity, EarlyActivationMilliseconds, Easing.OutQuint) .Then() - .FadeOut(timingPoint.BeatLength - 80, Easing.OutSine); + .FadeOut(timingPoint.BeatLength - fade_length, Easing.OutSine); } } } From 52557da335b1fd0374deb8c2447666d1a4153ef8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 14:06:23 +0900 Subject: [PATCH 0414/1621] Add test coverage --- .../TestSceneHitCircleKiai.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs new file mode 100644 index 0000000000..2bce8fa7f2 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs @@ -0,0 +1,30 @@ +// 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.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneHitCircleKiai : TestSceneHitCircle + { + [SetUp] + public void SetUp() => Schedule(() => + { + var controlPointInfo = new ControlPointInfo(); + + controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 }); + controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); + + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + ControlPointInfo = controlPointInfo + }); + + // track needs to be playing for BeatSyncedContainer to work. + Beatmap.Value.Track.Start(); + }); + } +} From 49fbd35e91a1c64b1ebb988590fee7bc26e788ef Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 4 Jun 2021 11:58:11 +0900 Subject: [PATCH 0415/1621] 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 a706ff63eddec695d2eaa039a1279fcdf086283a Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 12:50:03 +0700 Subject: [PATCH 0416/1621] change sprite text to text flow --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index 1c5e11a7ca..d91076e40a 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -54,26 +54,30 @@ namespace osu.Game.Overlays.Wiki [Resolved] private OverlayScrollContainer scrollContainer { get; set; } - private readonly OsuSpriteText spriteText; - private readonly MarkdownHeading target; + private readonly OsuTextFlowContainer textFlow; + public TocTitle(string text, MarkdownHeading target, bool subtitle = false) { this.target = target; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Child = spriteText = new OsuSpriteText + Child = textFlow = new OsuTextFlowContainer(t => { - Text = text, - Font = OsuFont.GetFont(size: subtitle ? 12 : 15), - }; + t.Font = OsuFont.GetFont(size: subtitle ? 12 : 15); + }).With(f => + { + f.AddText(text); + f.RelativeSizeAxes = Axes.X; + f.AutoSizeAxes = Axes.Y; + }); Margin = new MarginPadding { Top = subtitle ? 5 : 10 }; Padding = new MarginPadding { Left = subtitle ? 10 : 0 }; } - protected override IEnumerable EffectTargets => new Drawable[] { spriteText }; + protected override IEnumerable EffectTargets => new Drawable[] { textFlow }; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) From f62a2747f67721cc333ca37c93bb8721287cdd7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 14:51:35 +0900 Subject: [PATCH 0417/1621] Add legacy implementation --- .../Skinning/Legacy/KiaiFlashingSprite.cs | 61 +++++++++++++++++++ .../Skinning/Legacy/LegacyMainCirclePiece.cs | 13 ++-- 2 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs new file mode 100644 index 0000000000..4a1d69ad41 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.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 osu.Framework.Audio.Track; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Rulesets.Osu.Skinning.Legacy +{ + internal class KiaiFlashingSprite : BeatSyncedContainer + { + private readonly Sprite mainSprite; + private readonly Sprite flashingSprite; + + public Texture Texture + { + set + { + mainSprite.Texture = value; + flashingSprite.Texture = value; + } + } + + private const float flash_opacity = 0.3f; + + public KiaiFlashingSprite() + { + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + mainSprite = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + flashingSprite = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + Blending = BlendingParameters.Additive, + } + }; + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + if (!effectPoint.KiaiMode) + return; + + flashingSprite + .FadeTo(flash_opacity) + .Then() + .FadeOut(timingPoint.BeatLength * 0.75f); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index ffbeea5e0e..822dad8523 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -32,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); } - private Container circleSprites; - private Sprite hitCircleSprite; - private Sprite hitCircleOverlay; + private Container circleSprites; + private Drawable hitCircleSprite; + private Drawable hitCircleOverlay; private SkinnableSpriteText hitCircleText; @@ -72,20 +71,20 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy InternalChildren = new Drawable[] { - circleSprites = new Container + circleSprites = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Children = new[] { - hitCircleSprite = new Sprite + hitCircleSprite = new KiaiFlashingSprite { Texture = baseTexture, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - hitCircleOverlay = new Sprite + hitCircleOverlay = new KiaiFlashingSprite { Texture = overlayTexture, Anchor = Anchor.Centre, From 996c15610668fedb30df9dd4d885b648a6fb82dc Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 4 Jun 2021 13:56:10 +0800 Subject: [PATCH 0418/1621] apply suggestions - apply 0 alpha to beatmap background if storyboard replaces it - use an AudioContainer to mute all samples coming from the storyboard --- .../BeatmapBackgroundWithStoryboard.cs | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 9695e93f5d..b9e5b3c08f 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -1,13 +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.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; namespace osu.Game.Graphics.Backgrounds @@ -22,24 +20,17 @@ namespace osu.Game.Graphics.Backgrounds [BackgroundDependencyLoader] private void load() { - var storyboard = new Storyboard { BeatmapInfo = Beatmap.BeatmapInfo }; - - foreach (var layer in storyboard.Layers) - { - if (layer.Name != "Fail") - layer.Elements = Beatmap.Storyboard.GetLayer(layer.Name).Elements.Where(e => !(e is StoryboardSampleInfo)).ToList(); - } - - if (!storyboard.HasDrawable) + if (!Beatmap.Storyboard.HasDrawable) return; - if (storyboard.ReplacesBackground) - { - Sprite.Texture = Texture.WhitePixel; - Sprite.Colour = Colour4.Black; - } + if (Beatmap.Storyboard.ReplacesBackground) + Sprite.Alpha = 0; - LoadComponentAsync(new DrawableStoryboard(storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }, AddInternal); + var audio = new AudioContainer { RelativeSizeAxes = Axes.Both }; + audio.Volume.Value = 0; + + AddInternal(audio); + LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }, audio.Add); } } } From 071c07586a42846447f135219f6f037e66de841c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 15:00:53 +0900 Subject: [PATCH 0419/1621] Increase music volume back to 80% for the time being --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7935815f38..9c3adba342 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -58,7 +58,7 @@ namespace osu.Game /// /// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects. /// - internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.5; + internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.8; public bool UseDevelopmentServer { get; } From 6c1fede18e0b0b133a89a7629f5aeb3db5439fe3 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 13:11:37 +0700 Subject: [PATCH 0420/1621] add wiki sidebar test scene --- .../Visual/Online/TestSceneWikiSidebar.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs new file mode 100644 index 0000000000..fd7dbbe3ff --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig.Parsers; +using Markdig.Syntax; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Game.Overlays; +using osu.Game.Overlays.Wiki; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneWikiSidebar : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Orange); + + [Cached] + private readonly OverlayScrollContainer scrollContainer = new OverlayScrollContainer(); + + private WikiSidebar sidebar; + + private readonly MarkdownHeading dummyHeading = new MarkdownHeading(new HeadingBlock(new HeadingBlockParser())); + + [SetUp] + public void SetUp() => Schedule(() => Child = sidebar = new WikiSidebar()); + + [Test] + public void TestNoContent() + { + AddStep("No Content", () => { }); + } + + [Test] + public void TestOnlyMainTitle() + { + AddStep("Add TOC", () => + { + for (var i = 0; i < 10; i++) + { + sidebar.AddToc($"This is a very long title {i + 1}", dummyHeading, 2); + } + }); + } + + [Test] + public void TestWithSubtitle() + { + AddStep("Add TOC", () => + { + for (var i = 0; i < 20; i++) + { + sidebar.AddToc($"This is a very long title {i + 1}", dummyHeading, i % 4 == 0 ? 2 : 3); + } + }); + } + } +} From 19a44d65c5df7a1c83dfe9ca3b73ee0fbeb63d7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 15:18:16 +0900 Subject: [PATCH 0421/1621] Tidy up code --- .../Backgrounds/BeatmapBackgroundWithStoryboard.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index b9e5b3c08f..6a42e83305 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -26,11 +26,12 @@ namespace osu.Game.Graphics.Backgrounds if (Beatmap.Storyboard.ReplacesBackground) Sprite.Alpha = 0; - var audio = new AudioContainer { RelativeSizeAxes = Axes.Both }; - audio.Volume.Value = 0; - - AddInternal(audio); - LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }, audio.Add); + LoadComponentAsync(new AudioContainer + { + RelativeSizeAxes = Axes.Both, + Volume = { Value = 0 }, + Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) } + }, AddInternal); } } } From 37c8c63fc566c115aa3f974b6d23bb12f62caa05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 16:18:04 +0900 Subject: [PATCH 0422/1621] Ensure all frames in an animation are retrieved from the same skin --- osu.Game/Skinning/LegacySkinExtensions.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index d8fb1fa664..6e8276c01e 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -54,9 +54,16 @@ namespace osu.Game.Skinning IEnumerable getTextures() { + ISkin lookupSource = null; + for (int i = 0; true; i++) { - if ((texture = source.GetTexture($"{componentName}{animationSeparator}{i}", wrapModeS, wrapModeT)) == null) + string frameName = $"{componentName}{animationSeparator}{i}"; + + // ensure all textures are retrieved from the same skin source. + lookupSource ??= source.FindProvider(s => s.GetTexture(frameName, wrapModeS, wrapModeT) != null); + + if ((texture = lookupSource?.GetTexture(frameName, wrapModeS, wrapModeT)) == null) break; yield return texture; From 5fa93661522841c3188a17724518616beed31335 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 16:22:16 +0900 Subject: [PATCH 0423/1621] 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 b373b120ff090d633863d312bc0b2cadf24d7255 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Jun 2021 16:31:50 +0900 Subject: [PATCH 0424/1621] Use general lifetime container for follow point container --- .../Connections/FollowPointConnection.cs | 22 +++---- .../Connections/FollowPointRenderer.cs | 64 ++++--------------- 2 files changed, 20 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index cda4715280..7effc4cfa7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Pooling; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections @@ -12,38 +13,31 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// Visualises the s between two s. /// - public class FollowPointConnection : PoolableDrawable + public class FollowPointConnection : PoolableDrawableWithLifetime { // Todo: These shouldn't be constants public const int SPACING = 32; public const double PREEMPT = 800; - public FollowPointLifetimeEntry Entry; public DrawablePool Pool; - protected override void PrepareForUse() + protected override void OnApply(FollowPointLifetimeEntry entry) { - base.PrepareForUse(); - - Entry.Invalidated += onEntryInvalidated; + base.OnApply(entry); + entry.Invalidated += refreshPoints; refreshPoints(); } - protected override void FreeAfterUse() + protected override void OnFree(FollowPointLifetimeEntry entry) { - base.FreeAfterUse(); - - Entry.Invalidated -= onEntryInvalidated; + base.OnFree(entry); + entry.Invalidated -= refreshPoints; // Return points to the pool. ClearInternal(false); - - Entry = null; } - private void onEntryInvalidated() => Scheduler.AddOnce(refreshPoints); - private void refreshPoints() { ClearInternal(false); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 3e85e528e8..21e6619444 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -6,43 +6,32 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Pooling; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { /// /// Visualises connections between s. /// - public class FollowPointRenderer : CompositeDrawable + public class FollowPointRenderer : PooledDrawableWithLifetimeContainer { - public override bool RemoveCompletedTransforms => false; - - public IReadOnlyList Entries => lifetimeEntries; + public new IReadOnlyList Entries => lifetimeEntries; private DrawablePool connectionPool; private DrawablePool pointPool; private readonly List lifetimeEntries = new List(); - private readonly Dictionary connectionsInUse = new Dictionary(); private readonly Dictionary startTimeMap = new Dictionary(); - private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); - - public FollowPointRenderer() - { - lifetimeManager.EntryBecameAlive += onEntryBecameAlive; - lifetimeManager.EntryBecameDead += onEntryBecameDead; - } [BackgroundDependencyLoader] private void load() { InternalChildren = new Drawable[] { - connectionPool = new DrawablePoolNoLifetime(1, 200), - pointPool = new DrawablePoolNoLifetime(50, 1000) + connectionPool = new DrawablePool(1, 200), + pointPool = new DrawablePool(50, 1000) }; } @@ -107,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections previousEntry.End = newEntry.Start; } - lifetimeManager.AddEntry(newEntry); + Add(newEntry); } private void removeEntry(OsuHitObject hitObject) @@ -118,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections entry.UnbindEvents(); lifetimeEntries.RemoveAt(index); - lifetimeManager.RemoveEntry(entry); + Remove(entry); if (index > 0) { @@ -131,30 +120,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } } - protected override bool CheckChildrenLife() + protected override FollowPointConnection GetDrawable(FollowPointLifetimeEntry entry) { - bool anyAliveChanged = base.CheckChildrenLife(); - anyAliveChanged |= lifetimeManager.Update(Time.Current); - return anyAliveChanged; - } - - private void onEntryBecameAlive(LifetimeEntry entry) - { - var connection = connectionPool.Get(c => - { - c.Entry = (FollowPointLifetimeEntry)entry; - c.Pool = pointPool; - }); - - connectionsInUse[entry] = connection; - - AddInternal(connection); - } - - private void onEntryBecameDead(LifetimeEntry entry) - { - RemoveInternal(connectionsInUse[entry]); - connectionsInUse.Remove(entry); + var connection = connectionPool.Get(); + connection.Pool = pointPool; + connection.Apply(entry); + return connection; } private void onStartTimeChanged(OsuHitObject hitObject) @@ -171,16 +142,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections entry.UnbindEvents(); lifetimeEntries.Clear(); } - - private class DrawablePoolNoLifetime : DrawablePool - where T : PoolableDrawable, new() - { - public override bool RemoveWhenNotAlive => false; - - public DrawablePoolNoLifetime(int initialSize, int? maximumSize = null) - : base(initialSize, maximumSize) - { - } - } } } From 50819ef91fb84d441d790f27ba3843b66734d73b Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 4 Jun 2021 16:41:52 +0900 Subject: [PATCH 0425/1621] 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 ae2165b3be51770a8fe8da1eaefb730e6a75e6de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 16:57:40 +0900 Subject: [PATCH 0426/1621] Fix incorrect xmldoc --- osu.Game/Skinning/ISkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index 1c3598abb4..09e79a5ff5 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -60,7 +60,7 @@ namespace osu.Game.Skinning IBindable GetConfig(TLookup lookup); /// - /// For the specified texture, find any potential skin that can fulfill the lookup. + /// Find the first (if any) skin that can fulfill the lookup. /// This should be used for cases where subsequent lookups (for related components) need to occur on the same skin. /// /// The skin to be used for subsequent lookups, or null if none is available. From 6d6c03eafe1980ee0c705ae4caf589bc4dc1c141 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 14:45:06 +0700 Subject: [PATCH 0427/1621] use linq to find first literal inline --- osu.Game/Overlays/Wiki/WikiArticlePage.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Wiki/WikiArticlePage.cs b/osu.Game/Overlays/Wiki/WikiArticlePage.cs index 99068d6919..a60bebdc4b 100644 --- a/osu.Game/Overlays/Wiki/WikiArticlePage.cs +++ b/osu.Game/Overlays/Wiki/WikiArticlePage.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using Markdig.Syntax; using Markdig.Syntax.Inlines; using osu.Framework.Graphics; @@ -66,7 +67,7 @@ namespace osu.Game.Overlays.Wiki protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) { var heading = base.CreateHeading(headingBlock); - var title = ((LiteralInline)headingBlock.Inline.FirstChild).Content.ToString(); + var title = ((LiteralInline)headingBlock.Inline.First(i => i is LiteralInline)).Content.ToString(); OnAddHeading(title, heading, headingBlock.Level); From bc892086fea7f5dedb1ada1ae396e744502e98e2 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Jun 2021 16:41:56 +0900 Subject: [PATCH 0428/1621] Resolve nullable inspection, enable nullable for `FollowPointLifetimeEntry` --- .../Connections/FollowPointConnection.cs | 12 +++++++----- .../Connections/FollowPointLifetimeEntry.cs | 17 ++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 7effc4cfa7..55f494d2f9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects; @@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections base.OnApply(entry); entry.Invalidated += refreshPoints; - refreshPoints(); + refreshPoints(entry); } protected override void OnFree(FollowPointLifetimeEntry entry) @@ -38,12 +39,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections ClearInternal(false); } - private void refreshPoints() + private void refreshPoints(FollowPointLifetimeEntry entry) { ClearInternal(false); - OsuHitObject start = Entry.Start; - OsuHitObject end = Entry.End; + OsuHitObject start = entry.Start; + OsuHitObject end = entry.End; + Debug.Assert(end != null, $"{nameof(FollowPointLifetimeEntry)} without end hit object should never be alive"); double startTime = start.GetEndTime(); @@ -88,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } // todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed. - Entry.LifetimeEnd = finalTransformEndTime; + entry.LifetimeEnd = finalTransformEndTime; } /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs index a167cb2f0f..a44d07d92c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using osu.Framework.Bindables; using osu.Framework.Graphics.Performance; @@ -11,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointLifetimeEntry : LifetimeEntry { - public event Action Invalidated; + public event Action? Invalidated; public readonly OsuHitObject Start; public FollowPointLifetimeEntry(OsuHitObject start) @@ -22,9 +24,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections bindEvents(); } - private OsuHitObject end; + private OsuHitObject? end; - public OsuHitObject End + public OsuHitObject? End { get => end; set @@ -56,11 +58,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public void UnbindEvents() { - if (Start != null) - { - Start.DefaultsApplied -= onDefaultsApplied; - Start.PositionBindable.ValueChanged -= onPositionChanged; - } + Start.DefaultsApplied -= onDefaultsApplied; + Start.PositionBindable.ValueChanged -= onPositionChanged; if (End != null) { @@ -92,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections LifetimeStart = fadeInTime; LifetimeEnd = double.MaxValue; // This will be set by the connection. - Invalidated?.Invoke(); + Invalidated?.Invoke(this); } } } From 0098ac276090076b85faf01c8e934a358d9ee1f5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Jun 2021 16:53:27 +0900 Subject: [PATCH 0429/1621] Remove one TODO It can be removed because pooled drawables are always ready, and `FollowPointConnection` is also ready when applied. --- .../Objects/Drawables/Connections/FollowPointConnection.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 55f494d2f9..36a4d1abe3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -83,13 +83,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections fp.FadeIn(end.TimeFadeIn); fp.ScaleTo(end.Scale, end.TimeFadeIn, Easing.Out); fp.MoveTo(pointEndPosition, end.TimeFadeIn, Easing.Out); - fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn).Expire(); - finalTransformEndTime = fadeOutTime + end.TimeFadeIn; + finalTransformEndTime = fp.LifetimeEnd; } } - // todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed. entry.LifetimeEnd = finalTransformEndTime; } From 8883d5e2d1839056234d402bfb9188d7d64eeb93 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 15:21:44 +0700 Subject: [PATCH 0430/1621] use heading block to get title string --- osu.Game/Overlays/Wiki/WikiArticlePage.cs | 7 ++---- osu.Game/Overlays/Wiki/WikiSidebar.cs | 26 ++++++++++++++++++++--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiArticlePage.cs b/osu.Game/Overlays/Wiki/WikiArticlePage.cs index a60bebdc4b..83a61db88d 100644 --- a/osu.Game/Overlays/Wiki/WikiArticlePage.cs +++ b/osu.Game/Overlays/Wiki/WikiArticlePage.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using Markdig.Syntax; -using Markdig.Syntax.Inlines; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; @@ -62,14 +60,13 @@ namespace osu.Game.Overlays.Wiki private class ArticleMarkdownContainer : WikiMarkdownContainer { - public Action OnAddHeading; + public Action OnAddHeading; protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) { var heading = base.CreateHeading(headingBlock); - var title = ((LiteralInline)headingBlock.Inline.First(i => i is LiteralInline)).Content.ToString(); - OnAddHeading(title, heading, headingBlock.Level); + OnAddHeading(headingBlock, heading); return heading; } diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index d91076e40a..0db4d41b8b 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using Markdig.Syntax; +using Markdig.Syntax.Inlines; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -38,17 +40,35 @@ namespace osu.Game.Overlays.Wiki }, }; - public void AddToc(string title, MarkdownHeading heading, int level) + public void AddToc(HeadingBlock headingBlock, MarkdownHeading heading) { - switch (level) + switch (headingBlock.Level) { case 2: case 3: - tableOfContents.Add(new TocTitle(title, heading, level == 3)); + string title = getTitle(headingBlock.Inline); + tableOfContents.Add(new TocTitle(title, heading, headingBlock.Level == 3)); break; } } + private string getTitle(ContainerInline containerInline) + { + foreach (var inline in containerInline) + { + switch (inline) + { + case LiteralInline literalInline: + return literalInline.Content.ToString(); + + case LinkInline { IsImage: false } linkInline: + return getTitle(linkInline); + } + } + + return string.Empty; + } + private class TocTitle : OsuHoverContainer { [Resolved] From 3bf70dea60ed87d447cdbe64bc03cfc843bfc7b1 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 15:51:23 +0700 Subject: [PATCH 0431/1621] fix test to using heading block --- .../Visual/Online/TestSceneWikiSidebar.cs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs index fd7dbbe3ff..a04a3eef6b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs @@ -3,9 +3,11 @@ using Markdig.Parsers; using Markdig.Syntax; +using Markdig.Syntax.Inlines; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers.Markdown; +using osu.Game.Graphics.Containers.Markdown; using osu.Game.Overlays; using osu.Game.Overlays.Wiki; @@ -21,8 +23,6 @@ namespace osu.Game.Tests.Visual.Online private WikiSidebar sidebar; - private readonly MarkdownHeading dummyHeading = new MarkdownHeading(new HeadingBlock(new HeadingBlockParser())); - [SetUp] public void SetUp() => Schedule(() => Child = sidebar = new WikiSidebar()); @@ -38,9 +38,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Add TOC", () => { for (var i = 0; i < 10; i++) - { - sidebar.AddToc($"This is a very long title {i + 1}", dummyHeading, 2); - } + addTitle($"This is a very long title {i + 1}"); }); } @@ -49,11 +47,23 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Add TOC", () => { - for (var i = 0; i < 20; i++) - { - sidebar.AddToc($"This is a very long title {i + 1}", dummyHeading, i % 4 == 0 ? 2 : 3); - } + for (var i = 0; i < 10; i++) + addTitle($"This is a very long title {i + 1}", i % 4 != 0); }); } + + private void addTitle(string text, bool subtitle = false) + { + var headingBlock = createHeadingBlock(text, subtitle ? 3 : 2); + sidebar.AddToc(headingBlock, createHeading(headingBlock)); + } + + private HeadingBlock createHeadingBlock(string text, int level = 2) => new HeadingBlock(new HeadingBlockParser()) + { + Inline = new ContainerInline().AppendChild(new LiteralInline(text)), + Level = level, + }; + + private MarkdownHeading createHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock); } } From a82eeb6daf15c2391a9c104ed2793c32958f20c7 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 16:00:26 +0700 Subject: [PATCH 0432/1621] tidy up sidebar test --- .../Visual/Online/TestSceneWikiSidebar.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs index a04a3eef6b..bf96fc86d9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs @@ -6,7 +6,6 @@ using Markdig.Syntax; using Markdig.Syntax.Inlines; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers.Markdown; using osu.Game.Graphics.Containers.Markdown; using osu.Game.Overlays; using osu.Game.Overlays.Wiki; @@ -54,16 +53,13 @@ namespace osu.Game.Tests.Visual.Online private void addTitle(string text, bool subtitle = false) { - var headingBlock = createHeadingBlock(text, subtitle ? 3 : 2); - sidebar.AddToc(headingBlock, createHeading(headingBlock)); + var headingBlock = new HeadingBlock(new HeadingBlockParser()) + { + Inline = new ContainerInline().AppendChild(new LiteralInline(text)), + Level = subtitle ? 3 : 2, + }; + var heading = new OsuMarkdownHeading(headingBlock); + sidebar.AddToc(headingBlock, heading); } - - private HeadingBlock createHeadingBlock(string text, int level = 2) => new HeadingBlock(new HeadingBlockParser()) - { - Inline = new ContainerInline().AppendChild(new LiteralInline(text)), - Level = level, - }; - - private MarkdownHeading createHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock); } } From d7da66d876ec5a99b2effc438dabbba9dbd446bf Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Jun 2021 18:41:02 +0900 Subject: [PATCH 0433/1621] Bring back scheduling of follow point update --- .../Connections/FollowPointConnection.cs | 15 +++++++++------ .../Connections/FollowPointLifetimeEntry.cs | 4 ++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 36a4d1abe3..001ea6c4ad 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects; @@ -26,26 +25,30 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { base.OnApply(entry); - entry.Invalidated += refreshPoints; - refreshPoints(entry); + entry.Invalidated += onEntryInvalidated; + refreshPoints(); } protected override void OnFree(FollowPointLifetimeEntry entry) { base.OnFree(entry); - entry.Invalidated -= refreshPoints; + entry.Invalidated -= onEntryInvalidated; // Return points to the pool. ClearInternal(false); } - private void refreshPoints(FollowPointLifetimeEntry entry) + private void onEntryInvalidated() => Scheduler.AddOnce(refreshPoints); + + private void refreshPoints() { ClearInternal(false); + var entry = Entry; + if (entry?.End == null) return; + OsuHitObject start = entry.Start; OsuHitObject end = entry.End; - Debug.Assert(end != null, $"{nameof(FollowPointLifetimeEntry)} without end hit object should never be alive"); double startTime = start.GetEndTime(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs index a44d07d92c..82bca0a4e2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointLifetimeEntry : LifetimeEntry { - public event Action? Invalidated; + public event Action? Invalidated; public readonly OsuHitObject Start; public FollowPointLifetimeEntry(OsuHitObject start) @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections LifetimeStart = fadeInTime; LifetimeEnd = double.MaxValue; // This will be set by the connection. - Invalidated?.Invoke(this); + Invalidated?.Invoke(); } } } From 181f1da3d3bb44c4c34da40c4f1b1b758707f815 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Jun 2021 19:46:50 +0900 Subject: [PATCH 0434/1621] Maintain catch hit explosion by lifetime entries - Fix hit explosion not showing when a replay is rewound to a time after a hit object is caught --- .../TestSceneCatcher.cs | 2 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 18 ++------- osu.Game.Rulesets.Catch/UI/HitExplosion.cs | 39 ++++++++----------- .../UI/HitExplosionContainer.cs | 22 +++++++++++ .../UI/HitExplosionEntry.cs | 23 +++++++++++ 5 files changed, 66 insertions(+), 38 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs create mode 100644 osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 517027a9fc..900691ecae 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true)); AddStep("catch fruit", () => attemptCatch(new Fruit())); AddAssert("correct hit lighting colour", () => - catcher.ChildrenOfType().First()?.ObjectColour == fruitColour); + catcher.ChildrenOfType().First()?.Entry?.ObjectColour == fruitColour); } [Test] diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 0d6a577d1e..75ed4c3c0f 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -126,8 +126,7 @@ namespace osu.Game.Rulesets.Catch.UI private float hyperDashTargetPosition; private Bindable hitLighting; - private readonly DrawablePool hitExplosionPool; - private readonly Container hitExplosionContainer; + private readonly HitExplosionContainer hitExplosionContainer; private readonly DrawablePool caughtFruitPool; private readonly DrawablePool caughtBananaPool; @@ -148,7 +147,6 @@ namespace osu.Game.Rulesets.Catch.UI InternalChildren = new Drawable[] { - hitExplosionPool = new DrawablePool(10), caughtFruitPool = new DrawablePool(50), caughtBananaPool = new DrawablePool(100), // less capacity is needed compared to fruit because droplet is not stacked @@ -173,7 +171,7 @@ namespace osu.Game.Rulesets.Catch.UI Anchor = Anchor.TopCentre, Alpha = 0, }, - hitExplosionContainer = new Container + hitExplosionContainer = new HitExplosionContainer { Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, @@ -297,7 +295,6 @@ namespace osu.Game.Rulesets.Catch.UI caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject); droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject); - hitExplosionContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject); } /// @@ -508,15 +505,8 @@ namespace osu.Game.Rulesets.Catch.UI return position; } - private void addLighting(CatchHitObject hitObject, float x, Color4 colour) - { - HitExplosion hitExplosion = hitExplosionPool.Get(); - hitExplosion.HitObject = hitObject; - hitExplosion.X = x; - hitExplosion.Scale = new Vector2(hitObject.Scale); - hitExplosion.ObjectColour = colour; - hitExplosionContainer.Add(hitExplosion); - } + private void addLighting(CatchHitObject hitObject, float x, Color4 colour) => + hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, x, hitObject.Scale, colour)); private CaughtObject getCaughtObject(PalpableCatchHitObject source) { diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs index 26627422e1..c1effa939e 100644 --- a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs @@ -5,31 +5,15 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Pooling; using osu.Framework.Utils; -using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects.Pooling; using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.UI { - public class HitExplosion : PoolableDrawable + public class HitExplosion : PoolableDrawableWithLifetime { - private Color4 objectColour; - public CatchHitObject HitObject; - - public Color4 ObjectColour - { - get => objectColour; - set - { - if (objectColour == value) return; - - objectColour = value; - onColourChanged(); - } - } - private readonly CircularContainer largeFaint; private readonly CircularContainer smallFaint; private readonly CircularContainer directionalGlow1; @@ -83,9 +67,19 @@ namespace osu.Game.Rulesets.Catch.UI }; } - protected override void PrepareForUse() + protected override void OnApply(HitExplosionEntry entry) { - base.PrepareForUse(); + X = entry.Position; + Scale = new Vector2(entry.Scale); + setColour(entry.ObjectColour); + + using (BeginAbsoluteSequence(entry.LifetimeStart)) + applyTransforms(); + } + + private void applyTransforms() + { + ClearTransforms(true); const double duration = 400; @@ -99,11 +93,10 @@ namespace osu.Game.Rulesets.Catch.UI directionalGlow1.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle); directionalGlow2.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle); - this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out); - Expire(true); + this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out).Expire(); } - private void onColourChanged() + private void setColour(Color4 objectColour) { const float roundness = 100; diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs b/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs new file mode 100644 index 0000000000..094d88243a --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs @@ -0,0 +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 osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Objects.Pooling; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class HitExplosionContainer : PooledDrawableWithLifetimeContainer + { + protected override bool RemoveRewoundEntry => true; + + private readonly DrawablePool pool; + + public HitExplosionContainer() + { + AddInternal(pool = new DrawablePool(10)); + } + + protected override HitExplosion GetDrawable(HitExplosionEntry entry) => pool.Get(d => d.Apply(entry)); + } +} diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs b/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs new file mode 100644 index 0000000000..d0dfdfcafd --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Performance; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class HitExplosionEntry : LifetimeEntry + { + public readonly float Position; + public readonly float Scale; + public readonly Color4 ObjectColour; + + public HitExplosionEntry(double startTime, float position, float scale, Color4 objectColour) + { + LifetimeStart = startTime; + Position = position; + Scale = scale; + ObjectColour = objectColour; + } + } +} From 5512231bf4be1108dd589cbda34caa6da4a1ef96 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Jun 2021 19:52:12 +0900 Subject: [PATCH 0435/1621] Add `NextSingle` of version taking output range to `StatelessRNG` --- osu.Game/Utils/StatelessRNG.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Utils/StatelessRNG.cs b/osu.Game/Utils/StatelessRNG.cs index 118b08fe30..cd169229e3 100644 --- a/osu.Game/Utils/StatelessRNG.cs +++ b/osu.Game/Utils/StatelessRNG.cs @@ -75,5 +75,10 @@ namespace osu.Game.Utils /// public static float NextSingle(int seed, int series = 0) => (float)(NextULong(seed, series) & ((1 << 24) - 1)) / (1 << 24); // float has 24-bit precision + + /// + /// Compute a random floating point value between and from given seed and series number. + /// + public static float NextSingle(float min, float max, int seed, int series = 0) => min + NextSingle(seed, series) * (max - min); } } From 8e20f90ed5b836c79e4fd591f6d6a3c5d741d7cc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Jun 2021 19:54:46 +0900 Subject: [PATCH 0436/1621] Use seeded RNG for catch explosion animation The animation is always the same when a replay is rewound or a beatmap is played multiple times. --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 2 +- osu.Game.Rulesets.Catch/UI/HitExplosion.cs | 9 +++++---- osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs | 4 +++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 75ed4c3c0f..1c29e8b20c 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -506,7 +506,7 @@ namespace osu.Game.Rulesets.Catch.UI } private void addLighting(CatchHitObject hitObject, float x, Color4 colour) => - hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, x, hitObject.Scale, colour)); + hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, x, hitObject.Scale, colour, hitObject.RandomSeed)); private CaughtObject getCaughtObject(PalpableCatchHitObject source) { diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs index c1effa939e..d9ab428231 100644 --- a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Pooling; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -74,10 +75,10 @@ namespace osu.Game.Rulesets.Catch.UI setColour(entry.ObjectColour); using (BeginAbsoluteSequence(entry.LifetimeStart)) - applyTransforms(); + applyTransforms(entry.RNGSeed); } - private void applyTransforms() + private void applyTransforms(int randomSeed) { ClearTransforms(true); @@ -90,8 +91,8 @@ namespace osu.Game.Rulesets.Catch.UI .FadeOut(duration * 2); const float angle_variangle = 15; // should be less than 45 - directionalGlow1.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle); - directionalGlow2.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle); + directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4); + directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5); this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out).Expire(); } diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs b/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs index d0dfdfcafd..b142962a8a 100644 --- a/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs +++ b/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs @@ -11,13 +11,15 @@ namespace osu.Game.Rulesets.Catch.UI public readonly float Position; public readonly float Scale; public readonly Color4 ObjectColour; + public readonly int RNGSeed; - public HitExplosionEntry(double startTime, float position, float scale, Color4 objectColour) + public HitExplosionEntry(double startTime, float position, float scale, Color4 objectColour, int rngSeed) { LifetimeStart = startTime; Position = position; Scale = scale; ObjectColour = objectColour; + RNGSeed = rngSeed; } } } From 009aa994d09eca2508af2f3d4ec4fb73fae3a7f6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 4 Jun 2021 21:58:07 +0900 Subject: [PATCH 0437/1621] Fix potential race incorrectly pausing the source clock --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index fcbc6fae15..3fbb55872b 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -100,7 +100,13 @@ namespace osu.Game.Screens.Play { // The source is stopped by a frequency fade first. if (isPaused.NewValue) - this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => AdjustableSource.Stop()); + { + this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => + { + if (IsPaused.Value == isPaused.NewValue) + AdjustableSource.Stop(); + }); + } else this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); } From 9f2a9608f2acbb7c4169bdc6b0c2e9cc7c4aaa84 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Fri, 4 Jun 2021 16:17:54 +0200 Subject: [PATCH 0438/1621] Rework slider positioning --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 87 ++++++++++++---------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 4dfadbb835..f8572cc28b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "It never gets boring!"; public override bool Ranked => false; + private const float slider_path_checking_rate = 10; + // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn private const float playfield_edge_ratio = 0.375f; @@ -74,22 +76,8 @@ namespace osu.Game.Rulesets.Osu.Mods // update end position as it may have changed as a result of the position update. current.EndPositionRandomised = current.PositionRandomised; - switch (hitObject) - { - case Slider slider: - // Shift nested objects the same distance as the slider got shifted in the randomisation process - // so that moveSliderIntoPlayfield() can determine their relative distances to slider.Position and thus minMargin - shiftNestedObjects(slider, Vector2.Subtract(slider.Position, current.PositionOriginal)); - - var oldPos = new Vector2(slider.Position.X, slider.Position.Y); - - moveSliderIntoPlayfield(slider, current); - - // Shift them again to move them to their final position after the slider got moved into the playfield - shiftNestedObjects(slider, Vector2.Subtract(slider.Position, oldPos)); - - break; - } + if (hitObject is Slider slider) + moveSliderIntoPlayfield(slider, current); previous = current; } @@ -146,34 +134,53 @@ namespace osu.Game.Rulesets.Osu.Mods /// private void moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) { - // Min. distances from the slider's position to the playfield border - var minMargin = new MarginPadding(); + var minMargin = getMinSliderMargin(slider); - foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderEndCircle)) - { - if (!(hitObject is OsuHitObject osuHitObject)) - continue; - - var relativePos = Vector2.Subtract(osuHitObject.Position, slider.Position); - - minMargin.Left = Math.Max(minMargin.Left, -relativePos.X); - minMargin.Right = Math.Max(minMargin.Right, relativePos.X); - minMargin.Top = Math.Max(minMargin.Top, -relativePos.Y); - minMargin.Bottom = Math.Max(minMargin.Bottom, relativePos.Y); - } - - if (slider.Position.X < minMargin.Left) - slider.Position = new Vector2(minMargin.Left, slider.Position.Y); - else if (slider.Position.X + minMargin.Right > OsuPlayfield.BASE_SIZE.X) - slider.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - minMargin.Right, slider.Position.Y); - - if (slider.Position.Y < minMargin.Top) - slider.Position = new Vector2(slider.Position.X, minMargin.Top); - else if (slider.Position.Y + minMargin.Bottom > OsuPlayfield.BASE_SIZE.Y) - slider.Position = new Vector2(slider.Position.X, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); + slider.Position = new Vector2( + Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right), + Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom) + ); currentObjectInfo.PositionRandomised = slider.Position; currentObjectInfo.EndPositionRandomised = slider.EndPosition; + + shiftNestedObjects(slider, Vector2.Subtract(currentObjectInfo.PositionRandomised, currentObjectInfo.PositionOriginal)); + } + + /// + /// Calculates the min. distances from the 's position to the playfield border for the slider to be fully inside of the playfield. + /// + private MarginPadding getMinSliderMargin(Slider slider) + { + var minMargin = new MarginPadding(); + Vector2 pos; + + for (double j = 0; j <= 1; j += 1 / (slider_path_checking_rate / 1000 * (slider.EndTime - slider.StartTime))) + { + pos = slider.Path.PositionAt(j); + updateMargin(); + } + + var repeat = (SliderRepeat)slider.NestedHitObjects.FirstOrDefault(o => o is SliderRepeat); + + if (repeat != null) + { + pos = repeat.Position - slider.Position; + updateMargin(); + } + + pos = slider.Path.PositionAt(1); + updateMargin(); + + return minMargin; + + void updateMargin() + { + minMargin.Left = Math.Max(minMargin.Left, -pos.X); + minMargin.Right = Math.Max(minMargin.Right, pos.X); + minMargin.Top = Math.Max(minMargin.Top, -pos.Y); + minMargin.Bottom = Math.Max(minMargin.Bottom, pos.Y); + } } /// From a0a6f3ef81021df70ccccbc792d6b2253b55e4f9 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Fri, 4 Jun 2021 16:23:03 +0200 Subject: [PATCH 0439/1621] Replace `Vector2` methods with math operators --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index f8572cc28b..3525eddd2c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Mods private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio; private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio; - private static readonly Vector2 playfield_middle = Vector2.Divide(OsuPlayfield.BASE_SIZE, 2); + private static readonly Vector2 playfield_middle = OsuPlayfield.BASE_SIZE / 2; private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Mods current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); - var position = Vector2.Add(previous.EndPositionRandomised, posRelativeToPrev); + var position = previous.EndPositionRandomised + posRelativeToPrev; // Move hit objects back into the playfield if they are outside of it, // which would sometimes happen during big jumps otherwise. @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Mods currentObjectInfo.PositionRandomised = slider.Position; currentObjectInfo.EndPositionRandomised = slider.EndPosition; - shiftNestedObjects(slider, Vector2.Subtract(currentObjectInfo.PositionRandomised, currentObjectInfo.PositionOriginal)); + shiftNestedObjects(slider, currentObjectInfo.PositionRandomised - currentObjectInfo.PositionOriginal); } /// @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(hitObject is OsuHitObject osuHitObject)) continue; - osuHitObject.Position = Vector2.Add(osuHitObject.Position, shift); + osuHitObject.Position += shift; } } From 6357d1363c36b2ece628648ea98d93c9b232ec13 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Fri, 4 Jun 2021 16:26:40 +0200 Subject: [PATCH 0440/1621] Add comment for `slider_path_checking_rate` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 3525eddd2c..e5c48ca96e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "It never gets boring!"; public override bool Ranked => false; + // How often per second getMinSliderMargin() checks if the slider is outside of the playfield private const float slider_path_checking_rate = 10; // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. From 32e41048ff0a482263f30a177ada14a8fe8925ae Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Fri, 4 Jun 2021 16:50:27 +0200 Subject: [PATCH 0441/1621] Fix `System.ArgumentException` caused by sliders bigger than the playfield --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index e5c48ca96e..6181b2257e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -173,6 +173,9 @@ namespace osu.Game.Rulesets.Osu.Mods pos = slider.Path.PositionAt(1); updateMargin(); + minMargin.Left = Math.Min(minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right); + minMargin.Top = Math.Min(minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); + return minMargin; void updateMargin() From b4f190c6ff51a4e1b0fbd3d2e44606d24bb7a847 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Fri, 4 Jun 2021 17:22:36 +0200 Subject: [PATCH 0442/1621] Rename iteration variable --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 6181b2257e..79f821a8ef 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -156,9 +156,9 @@ namespace osu.Game.Rulesets.Osu.Mods var minMargin = new MarginPadding(); Vector2 pos; - for (double j = 0; j <= 1; j += 1 / (slider_path_checking_rate / 1000 * (slider.EndTime - slider.StartTime))) + for (double i = 0; i <= 1; i += 1 / (slider_path_checking_rate / 1000 * (slider.EndTime - slider.StartTime))) { - pos = slider.Path.PositionAt(j); + pos = slider.Path.PositionAt(i); updateMargin(); } From 70c64af25e001ca8d097c2f12d4626e21bb39fdb Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 23:31:51 +0700 Subject: [PATCH 0443/1621] rename toc entry --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index 0db4d41b8b..63a1ce3ffb 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Wiki case 2: case 3: string title = getTitle(headingBlock.Inline); - tableOfContents.Add(new TocTitle(title, heading, headingBlock.Level == 3)); + tableOfContents.Add(new TableOfContentsEntry(title, heading, headingBlock.Level == 3)); break; } } @@ -69,7 +69,7 @@ namespace osu.Game.Overlays.Wiki return string.Empty; } - private class TocTitle : OsuHoverContainer + private class TableOfContentsEntry : OsuHoverContainer { [Resolved] private OverlayScrollContainer scrollContainer { get; set; } @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.Wiki private readonly OsuTextFlowContainer textFlow; - public TocTitle(string text, MarkdownHeading target, bool subtitle = false) + public TableOfContentsEntry(string text, MarkdownHeading target, bool subtitle = false) { this.target = target; From 5febbe453086d874e0b893a8bca850867981492b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 23:32:42 +0700 Subject: [PATCH 0444/1621] rename method add entry --- osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs | 2 +- osu.Game/Overlays/Wiki/WikiArticlePage.cs | 2 +- osu.Game/Overlays/Wiki/WikiSidebar.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs index bf96fc86d9..b4f1997bb0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiSidebar.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online Level = subtitle ? 3 : 2, }; var heading = new OsuMarkdownHeading(headingBlock); - sidebar.AddToc(headingBlock, heading); + sidebar.AddEntry(headingBlock, heading); } } } diff --git a/osu.Game/Overlays/Wiki/WikiArticlePage.cs b/osu.Game/Overlays/Wiki/WikiArticlePage.cs index 83a61db88d..60982b0aa9 100644 --- a/osu.Game/Overlays/Wiki/WikiArticlePage.cs +++ b/osu.Game/Overlays/Wiki/WikiArticlePage.cs @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Wiki Left = 30, Right = 50, }, - OnAddHeading = sidebar.AddToc, + OnAddHeading = sidebar.AddEntry, } }, }; diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index 63a1ce3ffb..d4c484b0fd 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Wiki }, }; - public void AddToc(HeadingBlock headingBlock, MarkdownHeading heading) + public void AddEntry(HeadingBlock headingBlock, MarkdownHeading heading) { switch (headingBlock.Level) { From a431ef6c4802c298c2b40bda1f4d658da7db1778 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 23:43:00 +0700 Subject: [PATCH 0445/1621] keep colour change when entry is clicked --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index d4c484b0fd..f2cb2c3100 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -74,6 +74,9 @@ namespace osu.Game.Overlays.Wiki [Resolved] private OverlayScrollContainer scrollContainer { get; set; } + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + private readonly MarkdownHeading target; private readonly OsuTextFlowContainer textFlow; @@ -100,7 +103,7 @@ namespace osu.Game.Overlays.Wiki protected override IEnumerable EffectTargets => new Drawable[] { textFlow }; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load() { IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; @@ -108,6 +111,7 @@ namespace osu.Game.Overlays.Wiki protected override bool OnClick(ClickEvent e) { + IdleColour = colourProvider.Light1; scrollContainer.ScrollTo(target); return base.OnClick(e); } From f07d4532d94f0f22f79408afdb517c3622c4d37e Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 23:48:27 +0700 Subject: [PATCH 0446/1621] move scroll to into action --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index f2cb2c3100..3de2c8addf 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -71,9 +71,6 @@ namespace osu.Game.Overlays.Wiki private class TableOfContentsEntry : OsuHoverContainer { - [Resolved] - private OverlayScrollContainer scrollContainer { get; set; } - [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -103,16 +100,16 @@ namespace osu.Game.Overlays.Wiki protected override IEnumerable EffectTargets => new Drawable[] { textFlow }; [BackgroundDependencyLoader] - private void load() + private void load(OverlayScrollContainer scrollContainer) { IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; + Action = () => scrollContainer.ScrollTo(target); } protected override bool OnClick(ClickEvent e) { IdleColour = colourProvider.Light1; - scrollContainer.ScrollTo(target); return base.OnClick(e); } } From 5ee77925e4ab345b93ccffde20a8e792d9fa4885 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 23:54:50 +0700 Subject: [PATCH 0447/1621] change WikiArticlePage to extends CompositeDrawable --- osu.Game/Overlays/Wiki/WikiArticlePage.cs | 63 ++++++++++++----------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiArticlePage.cs b/osu.Game/Overlays/Wiki/WikiArticlePage.cs index 60982b0aa9..0061bff8ea 100644 --- a/osu.Game/Overlays/Wiki/WikiArticlePage.cs +++ b/osu.Game/Overlays/Wiki/WikiArticlePage.cs @@ -10,7 +10,7 @@ using osu.Game.Overlays.Wiki.Markdown; namespace osu.Game.Overlays.Wiki { - public class WikiArticlePage : GridContainer + public class WikiArticlePage : CompositeDrawable { public Container SidebarContainer { get; } @@ -18,42 +18,47 @@ namespace osu.Game.Overlays.Wiki { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - }; - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - }; WikiSidebar sidebar; - Content = new[] + InternalChild = new GridContainer { - new Drawable[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + RowDimensions = new[] { - SidebarContainer = new Container + new Dimension(GridSizeMode.AutoSize), + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { - AutoSizeAxes = Axes.X, - Child = sidebar = new WikiSidebar(), - }, - new ArticleMarkdownContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - CurrentPath = currentPath, - Text = markdown, - DocumentMargin = new MarginPadding(0), - DocumentPadding = new MarginPadding + SidebarContainer = new Container { - Vertical = 20, - Left = 30, - Right = 50, + AutoSizeAxes = Axes.X, + Child = sidebar = new WikiSidebar(), }, - OnAddHeading = sidebar.AddEntry, - } + new ArticleMarkdownContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + CurrentPath = currentPath, + Text = markdown, + DocumentMargin = new MarginPadding(0), + DocumentPadding = new MarginPadding + { + Vertical = 20, + Left = 30, + Right = 50, + }, + OnAddHeading = sidebar.AddEntry, + } + }, }, }; } From 4cf3381d0bcbe0d195890a16406fe6eaf5b21a72 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 4 Jun 2021 23:59:11 +0700 Subject: [PATCH 0448/1621] use wiki article page when failed fetch --- osu.Game/Overlays/WikiOverlay.cs | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 5beb5d50e6..fc24820f3c 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -11,7 +11,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Wiki; -using osu.Game.Overlays.Wiki.Markdown; namespace osu.Game.Overlays { @@ -139,20 +138,8 @@ namespace osu.Game.Overlays private void onFail() { - LoadDisplay(new WikiMarkdownContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - CurrentPath = $@"{api.WebsiteRootUrl}/wiki/", - Text = $"Something went wrong when trying to fetch page \"{path.Value}\".\n\n[Return to the main page](Main_Page).", - DocumentMargin = new MarginPadding(0), - DocumentPadding = new MarginPadding - { - Vertical = 20, - Left = 30, - Right = 50, - }, - }); + LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/", + $"Something went wrong when trying to fetch page \"{path.Value}\".\n\n[Return to the main page](Main_Page).")); } private void showParentPage() From 1084906d405ed33ad71eaac08dc94ac44556a33d Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 5 Jun 2021 03:30:21 +0200 Subject: [PATCH 0449/1621] Add DummyAPIAccess one parent up --- 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 198d22fedd..b8a266fc98 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual { dummyAPI = new DummyAPIAccess(); Dependencies.CacheAs(dummyAPI); - Add(dummyAPI); + base.Content.Add(dummyAPI); } return Dependencies; From b746fe7c03c3ca863ddd9825111b3b9b95d9de14 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 5 Jun 2021 11:03:49 +0200 Subject: [PATCH 0450/1621] 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 0451/1621] 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 c099751ad189f119dee015720753cf911debb623 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 5 Jun 2021 18:26:03 +0700 Subject: [PATCH 0452/1621] use plain if check in switch case --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index 3de2c8addf..c95ee3a8c3 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -61,8 +61,11 @@ namespace osu.Game.Overlays.Wiki case LiteralInline literalInline: return literalInline.Content.ToString(); - case LinkInline { IsImage: false } linkInline: - return getTitle(linkInline); + case LinkInline linkInline: + if (!linkInline.IsImage) + return getTitle(linkInline); + + break; } } From 958bddc8cb9d4443d729a6a54a0fb2bc5873e249 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 5 Jun 2021 18:30:28 +0700 Subject: [PATCH 0453/1621] remove onclick in toc entry --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index c95ee3a8c3..b4e97e4a7b 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -109,12 +108,6 @@ namespace osu.Game.Overlays.Wiki HoverColour = colourProvider.Light1; Action = () => scrollContainer.ScrollTo(target); } - - protected override bool OnClick(ClickEvent e) - { - IdleColour = colourProvider.Light1; - return base.OnClick(e); - } } } } From ce4bcda8032d19d9364cad23b5882536d411886b Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 5 Jun 2021 14:02:48 +0200 Subject: [PATCH 0454/1621] 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 0455/1621] 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 55f3a328a4bb7cb2e460261e242734bb44ad5eaa Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 5 Jun 2021 19:46:46 +0700 Subject: [PATCH 0456/1621] add WikiTableOfContents --- osu.Game/Overlays/Wiki/WikiTableOfContents.cs | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 osu.Game/Overlays/Wiki/WikiTableOfContents.cs diff --git a/osu.Game/Overlays/Wiki/WikiTableOfContents.cs b/osu.Game/Overlays/Wiki/WikiTableOfContents.cs new file mode 100644 index 0000000000..857ec27020 --- /dev/null +++ b/osu.Game/Overlays/Wiki/WikiTableOfContents.cs @@ -0,0 +1,108 @@ +// 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.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.Wiki +{ + public class WikiTableOfContents : CompositeDrawable + { + private readonly FillFlowContainer content; + + private FillFlowContainer lastItem; + + private FillFlowContainer lastSubsection; + + public WikiTableOfContents() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = content = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }; + } + + public void AddEntry(string title, MarkdownHeading target, bool subtitle = false) + { + var entry = new TableOfContentsEntry(title, target, subtitle); + + if (subtitle) + { + lastSubsection ??= new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = 10 }, + }; + + lastSubsection.Add(entry); + + return; + } + + if (lastSubsection != null) + { + lastItem.Add(lastSubsection); + lastItem.Margin = new MarginPadding { Bottom = 10 }; + lastSubsection = null; + } + + content.Add(lastItem = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Bottom = 5 }, + Child = entry, + }); + } + + private class TableOfContentsEntry : OsuHoverContainer + { + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private readonly MarkdownHeading target; + + private readonly OsuTextFlowContainer textFlow; + + public TableOfContentsEntry(string text, MarkdownHeading target, bool subtitle = false) + { + this.target = target; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Child = textFlow = new OsuTextFlowContainer(t => + { + t.Font = OsuFont.GetFont(size: subtitle ? 12 : 15); + }).With(f => + { + f.AddText(text); + f.RelativeSizeAxes = Axes.X; + f.AutoSizeAxes = Axes.Y; + }); + Margin = new MarginPadding { Bottom = 2 }; + } + + protected override IEnumerable EffectTargets => new Drawable[] { textFlow }; + + [BackgroundDependencyLoader] + private void load(OverlayScrollContainer scrollContainer) + { + IdleColour = colourProvider.Light2; + HoverColour = colourProvider.Light1; + Action = () => scrollContainer.ScrollTo(target); + } + } + } +} From 9f45a2862358e98b9ba819dea5faec24dcd431b2 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 5 Jun 2021 19:47:00 +0700 Subject: [PATCH 0457/1621] use WikiTableOfContents in WikiSidebar --- osu.Game/Overlays/Wiki/WikiSidebar.cs | 55 ++------------------------- 1 file changed, 4 insertions(+), 51 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiSidebar.cs b/osu.Game/Overlays/Wiki/WikiSidebar.cs index b4e97e4a7b..ee4e195f3f 100644 --- a/osu.Game/Overlays/Wiki/WikiSidebar.cs +++ b/osu.Game/Overlays/Wiki/WikiSidebar.cs @@ -1,22 +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 System.Collections.Generic; using Markdig.Syntax; using Markdig.Syntax.Inlines; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Wiki { public class WikiSidebar : OverlaySidebar { - private FillFlowContainer tableOfContents; + private WikiTableOfContents tableOfContents; protected override Drawable CreateContent() => new FillFlowContainer { @@ -29,13 +26,9 @@ namespace osu.Game.Overlays.Wiki { Text = "CONTENTS", Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Margin = new MarginPadding { Bottom = 5 }, }, - tableOfContents = new FillFlowContainer - { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } + tableOfContents = new WikiTableOfContents(), }, }; @@ -45,8 +38,7 @@ namespace osu.Game.Overlays.Wiki { case 2: case 3: - string title = getTitle(headingBlock.Inline); - tableOfContents.Add(new TableOfContentsEntry(title, heading, headingBlock.Level == 3)); + tableOfContents.AddEntry(getTitle(headingBlock.Inline), heading, headingBlock.Level == 3); break; } } @@ -70,44 +62,5 @@ namespace osu.Game.Overlays.Wiki return string.Empty; } - - private class TableOfContentsEntry : OsuHoverContainer - { - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - - private readonly MarkdownHeading target; - - private readonly OsuTextFlowContainer textFlow; - - public TableOfContentsEntry(string text, MarkdownHeading target, bool subtitle = false) - { - this.target = target; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Child = textFlow = new OsuTextFlowContainer(t => - { - t.Font = OsuFont.GetFont(size: subtitle ? 12 : 15); - }).With(f => - { - f.AddText(text); - f.RelativeSizeAxes = Axes.X; - f.AutoSizeAxes = Axes.Y; - }); - Margin = new MarginPadding { Top = subtitle ? 5 : 10 }; - Padding = new MarginPadding { Left = subtitle ? 10 : 0 }; - } - - protected override IEnumerable EffectTargets => new Drawable[] { textFlow }; - - [BackgroundDependencyLoader] - private void load(OverlayScrollContainer scrollContainer) - { - IdleColour = colourProvider.Light2; - HoverColour = colourProvider.Light1; - Action = () => scrollContainer.ScrollTo(target); - } - } } } From 248e90df6d492baebd808188b1fa47f96527f7f7 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 5 Jun 2021 15:55:58 +0200 Subject: [PATCH 0458/1621] 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 0459/1621] 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 f59263932a1a74984658bd61df832b1724190c6d Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 5 Jun 2021 17:04:58 +0200 Subject: [PATCH 0460/1621] Use `SliderPath.GetPathToProgress` for getting the `SliderPath`'s positions --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 25 +++++++--------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 79f821a8ef..2d61c64fcb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.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 osu.Framework.Graphics; using osu.Framework.Utils; @@ -23,9 +24,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "It never gets boring!"; public override bool Ranked => false; - // How often per second getMinSliderMargin() checks if the slider is outside of the playfield - private const float slider_path_checking_rate = 10; - // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn private const float playfield_edge_ratio = 0.375f; @@ -154,31 +152,24 @@ namespace osu.Game.Rulesets.Osu.Mods private MarginPadding getMinSliderMargin(Slider slider) { var minMargin = new MarginPadding(); - Vector2 pos; - for (double i = 0; i <= 1; i += 1 / (slider_path_checking_rate / 1000 * (slider.EndTime - slider.StartTime))) - { - pos = slider.Path.PositionAt(i); - updateMargin(); - } + var pathPositions = new List(); + slider.Path.GetPathToProgress(pathPositions, 0, 1); + + foreach (var pos in pathPositions) + updateMargin(pos); var repeat = (SliderRepeat)slider.NestedHitObjects.FirstOrDefault(o => o is SliderRepeat); if (repeat != null) - { - pos = repeat.Position - slider.Position; - updateMargin(); - } - - pos = slider.Path.PositionAt(1); - updateMargin(); + updateMargin(repeat.Position - slider.Position); minMargin.Left = Math.Min(minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right); minMargin.Top = Math.Min(minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); return minMargin; - void updateMargin() + void updateMargin(Vector2 pos) { minMargin.Left = Math.Max(minMargin.Left, -pos.X); minMargin.Right = Math.Max(minMargin.Right, pos.X); From b214f2ae0e39a08e6763c8943d9e63991b996568 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 5 Jun 2021 17:13:08 +0200 Subject: [PATCH 0461/1621] Remove `repeat` and simplify `getMinSliderMargin` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 2d61c64fcb..c282a919ea 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -151,31 +151,23 @@ namespace osu.Game.Rulesets.Osu.Mods /// private MarginPadding getMinSliderMargin(Slider slider) { - var minMargin = new MarginPadding(); - var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); + var minMargin = new MarginPadding(); + foreach (var pos in pathPositions) - updateMargin(pos); - - var repeat = (SliderRepeat)slider.NestedHitObjects.FirstOrDefault(o => o is SliderRepeat); - - if (repeat != null) - updateMargin(repeat.Position - slider.Position); - - minMargin.Left = Math.Min(minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right); - minMargin.Top = Math.Min(minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); - - return minMargin; - - void updateMargin(Vector2 pos) { minMargin.Left = Math.Max(minMargin.Left, -pos.X); minMargin.Right = Math.Max(minMargin.Right, pos.X); minMargin.Top = Math.Max(minMargin.Top, -pos.Y); minMargin.Bottom = Math.Max(minMargin.Bottom, pos.Y); } + + minMargin.Left = Math.Min(minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right); + minMargin.Top = Math.Min(minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); + + return minMargin; } /// From b97f31f31414a8cc6c284ce2239d2cdecaf64500 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sat, 5 Jun 2021 19:03:11 +0200 Subject: [PATCH 0462/1621] 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 525c16419a5587a19a6173034e5d30e15ad2143b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 6 Jun 2021 08:37:03 +0700 Subject: [PATCH 0463/1621] use container for main title and sub title table of contents --- osu.Game/Overlays/Wiki/WikiTableOfContents.cs | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiTableOfContents.cs b/osu.Game/Overlays/Wiki/WikiTableOfContents.cs index 857ec27020..6c9f7d1536 100644 --- a/osu.Game/Overlays/Wiki/WikiTableOfContents.cs +++ b/osu.Game/Overlays/Wiki/WikiTableOfContents.cs @@ -15,9 +15,9 @@ namespace osu.Game.Overlays.Wiki { private readonly FillFlowContainer content; - private FillFlowContainer lastItem; + private Container lastMainTitle; - private FillFlowContainer lastSubsection; + private Container lastSubTitle; public WikiTableOfContents() { @@ -37,29 +37,26 @@ namespace osu.Game.Overlays.Wiki if (subtitle) { - lastSubsection ??= new FillFlowContainer + lastMainTitle.Margin = new MarginPadding(0); + + if (lastSubTitle != null) + lastSubTitle.Margin = new MarginPadding(0); + + content.Add(lastSubTitle = new Container { - Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = 10 }, - }; - - lastSubsection.Add(entry); + Margin = new MarginPadding { Bottom = 10 }, + Child = entry, + }); return; } - if (lastSubsection != null) - { - lastItem.Add(lastSubsection); - lastItem.Margin = new MarginPadding { Bottom = 10 }; - lastSubsection = null; - } + lastSubTitle = null; - content.Add(lastItem = new FillFlowContainer + content.Add(lastMainTitle = new Container { - Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Margin = new MarginPadding { Bottom = 5 }, @@ -92,6 +89,7 @@ namespace osu.Game.Overlays.Wiki f.AutoSizeAxes = Axes.Y; }); Margin = new MarginPadding { Bottom = 2 }; + Padding = new MarginPadding { Left = subtitle ? 10 : 0 }; } protected override IEnumerable EffectTargets => new Drawable[] { textFlow }; From 39f99bf785676c443f37248668eb9bf45006032e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Jun 2021 11:08:54 +0900 Subject: [PATCH 0464/1621] Move `FindProvider` to `ISkinSource` --- osu.Game/Skinning/ISkin.cs | 9 ------- osu.Game/Skinning/ISkinSource.cs | 9 +++++++ osu.Game/Skinning/LegacySkin.cs | 27 +++++-------------- osu.Game/Skinning/Skin.cs | 2 -- osu.Game/Skinning/SkinManager.cs | 2 +- .../Beatmaps/LegacyBeatmapSkinColourTest.cs | 2 ++ 6 files changed, 18 insertions(+), 33 deletions(-) diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index 09e79a5ff5..73f7cf6d39 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.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 JetBrains.Annotations; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -58,13 +57,5 @@ namespace osu.Game.Skinning /// A matching value boxed in an , or null if unavailable. [CanBeNull] IBindable GetConfig(TLookup lookup); - - /// - /// Find the first (if any) skin that can fulfill the lookup. - /// This should be used for cases where subsequent lookups (for related components) need to occur on the same skin. - /// - /// The skin to be used for subsequent lookups, or null if none is available. - [CanBeNull] - ISkin FindProvider(Func lookupFunction); } } diff --git a/osu.Game/Skinning/ISkinSource.cs b/osu.Game/Skinning/ISkinSource.cs index 337d2a87a4..c7ebe91d64 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 JetBrains.Annotations; namespace osu.Game.Skinning { @@ -11,5 +12,13 @@ namespace osu.Game.Skinning public interface ISkinSource : ISkin { event Action SourceChanged; + + /// + /// Find the first (if any) skin that can fulfill the lookup. + /// This should be used for cases where subsequent lookups (for related components) need to occur on the same skin. + /// + /// The skin to be used for subsequent lookups, or null if none is available. + [CanBeNull] + ISkin FindProvider(Func lookupFunction); } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index d2d7cc4d86..8f1895883d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -54,9 +54,6 @@ namespace osu.Game.Skinning private readonly Dictionary maniaConfigurations = new Dictionary(); - [CanBeNull] - private readonly DefaultLegacySkin legacyDefaultFallback; - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public LegacySkin(SkinInfo skin, IStorageResourceProvider resources) : this(skin, new LegacySkinResourceStore(skin, resources.Files), resources, "skin.ini") @@ -73,9 +70,6 @@ namespace osu.Game.Skinning protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore storage, [CanBeNull] IStorageResourceProvider resources, string configurationFilename) : base(skin, resources) { - if (resources != null) - legacyDefaultFallback = CreateFallbackSkin(storage, resources); - using (var stream = storage?.GetStream(configurationFilename)) { if (stream != null) @@ -158,7 +152,7 @@ namespace osu.Game.Skinning return genericLookup(lookup); } - return legacyDefaultFallback?.GetConfig(lookup); + return null; } private IBindable lookupForMania(LegacyManiaSkinConfigurationLookup maniaLookup) @@ -335,7 +329,7 @@ namespace osu.Game.Skinning { } - return legacyDefaultFallback?.GetConfig(lookup); + return null; } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -406,6 +400,7 @@ namespace osu.Game.Skinning return null; case GameplaySkinComponent resultComponent: + // TODO: this should be inside the judgement pieces. Func createDrawable = () => getJudgementAnimation(resultComponent.Component); // kind of wasteful that we throw this away, but should do for now. @@ -427,7 +422,7 @@ namespace osu.Game.Skinning if (animation != null) return animation; - return legacyDefaultFallback?.GetDrawableComponent(component); + return null; } private Texture getParticleTexture(HitResult result) @@ -487,7 +482,7 @@ namespace osu.Game.Skinning return texture; } - return legacyDefaultFallback?.GetTexture(componentName, wrapModeS, wrapModeT); + return null; } public override ISample GetSample(ISampleInfo sampleInfo) @@ -511,17 +506,7 @@ namespace osu.Game.Skinning } } - return legacyDefaultFallback?.GetSample(sampleInfo); - } - - public override ISkin FindProvider(Func lookupFunction) - { - var source = base.FindProvider(lookupFunction); - - if (source != null) - return source; - - return legacyDefaultFallback?.FindProvider(lookupFunction); + return null; } private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index c12e9a64c2..b6cb8fc7a4 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -35,8 +35,6 @@ namespace osu.Game.Skinning public abstract IBindable GetConfig(TLookup lookup); - public virtual ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; - protected Skin(SkinInfo skin, IStorageResourceProvider resources) { SkinInfo = skin; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index fa4f657882..d373618232 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -212,7 +212,7 @@ namespace osu.Game.Skinning public IBindable GetConfig(TLookup lookup) => CurrentSkin.Value.GetConfig(lookup); - public ISkin FindProvider(Func lookupFunction) => CurrentSkin.Value.FindProvider(lookupFunction); + public ISkin FindProvider(Func lookupFunction) => lookupFunction(CurrentSkin.Value) ? CurrentSkin.Value : null; #region IResourceStorageProvider diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs index 0a7fb1483d..2540b6d7da 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -158,6 +158,8 @@ namespace osu.Game.Tests.Beatmaps add { } remove { } } + + public ISkin FindProvider(Func lookupFunction) => null; } } } From b87a5956dd62de3fb77532073022ee6ac0a08e21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Jun 2021 12:17:55 +0900 Subject: [PATCH 0465/1621] Add fallback logic to `SkinManager` --- osu.Game/Skinning/SkinManager.cs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index d373618232..9aa2d90064 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -204,16 +204,34 @@ namespace osu.Game.Skinning public event Action SourceChanged; - public Drawable GetDrawableComponent(ISkinComponent component) => CurrentSkin.Value.GetDrawableComponent(component); + public Drawable GetDrawableComponent(ISkinComponent component) => lookupWithFallback(s => s.GetDrawableComponent(component)); - public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => CurrentSkin.Value.GetTexture(componentName, wrapModeS, wrapModeT); + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => lookupWithFallback(s => s.GetTexture(componentName, wrapModeS, wrapModeT)); - public ISample GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo); + public ISample GetSample(ISampleInfo sampleInfo) => lookupWithFallback(s => s.GetSample(sampleInfo)); - public IBindable GetConfig(TLookup lookup) => CurrentSkin.Value.GetConfig(lookup); + public IBindable GetConfig(TLookup lookup) => lookupWithFallback(s => s.GetConfig(lookup)); public ISkin FindProvider(Func lookupFunction) => lookupFunction(CurrentSkin.Value) ? CurrentSkin.Value : null; + private Skin defaultLegacySkin; + + private T lookupWithFallback(Func func) + where T : class + { + var selectedSkin = func(CurrentSkin.Value); + + if (selectedSkin != null) + return selectedSkin; + + defaultLegacySkin ??= new DefaultLegacySkin(this); + + if (CurrentSkin.Value is LegacySkin) + return func(defaultLegacySkin); + + return null; + } + #region IResourceStorageProvider AudioManager IStorageResourceProvider.AudioManager => audio; From b904fa6615ad210afc94e874a3861572e9bb0499 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Jun 2021 12:37:42 +0900 Subject: [PATCH 0466/1621] Revert "Ensure all frames in an animation are retrieved from the same skin" This reverts commit 37c8c63fc566c115aa3f974b6d23bb12f62caa05. --- osu.Game/Skinning/LegacySkinExtensions.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 6e8276c01e..d8fb1fa664 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -54,16 +54,9 @@ namespace osu.Game.Skinning IEnumerable getTextures() { - ISkin lookupSource = null; - for (int i = 0; true; i++) { - string frameName = $"{componentName}{animationSeparator}{i}"; - - // ensure all textures are retrieved from the same skin source. - lookupSource ??= source.FindProvider(s => s.GetTexture(frameName, wrapModeS, wrapModeT) != null); - - if ((texture = lookupSource?.GetTexture(frameName, wrapModeS, wrapModeT)) == null) + if ((texture = source.GetTexture($"{componentName}{animationSeparator}{i}", wrapModeS, wrapModeT)) == null) break; yield return texture; From c452715bf190e7aaa8aef11612cd0f56e35b5561 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Sun, 6 Jun 2021 01:18:30 -0400 Subject: [PATCH 0467/1621] 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 0468/1621] 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 0469/1621] 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 0470/1621] 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 ed733ee648b91223a3d758d94bdaf90b3a2a6b11 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 6 Jun 2021 20:19:39 +0700 Subject: [PATCH 0471/1621] directly using table of content entry in wiki table of contents --- osu.Game/Overlays/Wiki/WikiTableOfContents.cs | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiTableOfContents.cs b/osu.Game/Overlays/Wiki/WikiTableOfContents.cs index 6c9f7d1536..77441c26ed 100644 --- a/osu.Game/Overlays/Wiki/WikiTableOfContents.cs +++ b/osu.Game/Overlays/Wiki/WikiTableOfContents.cs @@ -15,9 +15,9 @@ namespace osu.Game.Overlays.Wiki { private readonly FillFlowContainer content; - private Container lastMainTitle; + private TableOfContentsEntry lastMainTitle; - private Container lastSubTitle; + private TableOfContentsEntry lastSubTitle; public WikiTableOfContents() { @@ -42,26 +42,14 @@ namespace osu.Game.Overlays.Wiki if (lastSubTitle != null) lastSubTitle.Margin = new MarginPadding(0); - content.Add(lastSubTitle = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Bottom = 10 }, - Child = entry, - }); + content.Add(lastSubTitle = entry.With(d => d.Margin = new MarginPadding { Bottom = 10 })); return; } lastSubTitle = null; - content.Add(lastMainTitle = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Bottom = 5 }, - Child = entry, - }); + content.Add(lastMainTitle = entry.With(d => d.Margin = new MarginPadding { Bottom = 5 })); } private class TableOfContentsEntry : OsuHoverContainer @@ -87,8 +75,8 @@ namespace osu.Game.Overlays.Wiki f.AddText(text); f.RelativeSizeAxes = Axes.X; f.AutoSizeAxes = Axes.Y; + f.Margin = new MarginPadding { Bottom = 2 }; }); - Margin = new MarginPadding { Bottom = 2 }; Padding = new MarginPadding { Left = subtitle ? 10 : 0 }; } From 9ebafb1ec0dcde3756fb681d826d85a465775a95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Jun 2021 22:26:27 +0900 Subject: [PATCH 0472/1621] Fix cursor trail logic --- .../TestSceneCursorTrail.cs | 18 ++++++++++++++---- .../Skinning/Legacy/LegacyCursor.cs | 6 ++++-- .../Skinning/Legacy/LegacyCursorTrail.cs | 8 +++++++- .../Legacy/OsuLegacySkinTransformer.cs | 12 ++++++++---- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index 9997660c2d..46274e779b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -39,18 +39,28 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestLegacySmoothCursorTrail() { - createTest(() => new LegacySkinContainer(false) + createTest(() => { - Child = new LegacyCursorTrail() + var skinContainer = new LegacySkinContainer(false); + var legacyCursorTrail = new LegacyCursorTrail(skinContainer); + + skinContainer.Child = legacyCursorTrail; + + return skinContainer; }); } [Test] public void TestLegacyDisjointCursorTrail() { - createTest(() => new LegacySkinContainer(true) + createTest(() => { - Child = new LegacyCursorTrail() + var skinContainer = new LegacySkinContainer(true); + var legacyCursorTrail = new LegacyCursorTrail(skinContainer); + + skinContainer.Child = legacyCursorTrail; + + return skinContainer; }); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs index 7a8555d991..b2ffc171be 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs @@ -11,10 +11,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class LegacyCursor : OsuCursorSprite { + private readonly ISkin skin; private bool spin; - public LegacyCursor() + public LegacyCursor(ISkin skin) { + this.skin = skin; Size = new Vector2(50); Anchor = Anchor.Centre; @@ -22,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load() { bool centre = skin.GetConfig(OsuSkinConfiguration.CursorCentre)?.Value ?? true; spin = skin.GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs index 0025576325..f6fd3e36ab 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs @@ -14,14 +14,20 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class LegacyCursorTrail : CursorTrail { + private readonly ISkin skin; private const double disjoint_trail_time_separation = 1000 / 60.0; private bool disjointTrail; private double lastTrailTime; private IBindable cursorSize; + public LegacyCursorTrail(ISkin skin) + { + this.skin = skin; + } + [BackgroundDependencyLoader] - private void load(ISkinSource skin, OsuConfigManager config) + private void load(OsuConfigManager config) { Texture = skin.GetTexture("cursortrail"); disjointTrail = skin.GetTexture("cursormiddle") == null; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 33693748d9..e3f32fb76f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -84,14 +84,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; case OsuSkinComponents.Cursor: - if (Source.GetTexture("cursor") != null) - return new LegacyCursor(); + var cursorProvider = Source.FindProvider(s => s.GetTexture("cursor") != null); + + if (cursorProvider != null) + return new LegacyCursor(cursorProvider); return null; case OsuSkinComponents.CursorTrail: - if (Source.GetTexture("cursortrail") != null) - return new LegacyCursorTrail(); + var trailProvider = Source.FindProvider(s => s.GetTexture("cursortrail") != null); + + if (trailProvider != null) + return new LegacyCursorTrail(trailProvider); return null; From 63346f6b756521bdac240777bcb26716cb2106ae Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Sun, 6 Jun 2021 09:40:58 -0400 Subject: [PATCH 0473/1621] 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 b5f145cfa92e08b26c7d6fb976fbe011ce7167cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Jun 2021 23:01:37 +0900 Subject: [PATCH 0474/1621] Use null propagation for animation lookups --- osu.Game/Skinning/LegacySkin.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 8f1895883d..337acee9e8 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -417,12 +417,7 @@ namespace osu.Game.Skinning break; } - var animation = this.GetAnimation(component.LookupName, false, false); - - if (animation != null) - return animation; - - return null; + return this.GetAnimation(component.LookupName, false, false); } private Texture getParticleTexture(HitResult result) From da1c38d5a907ae2e4122b4d133f38a60949d0435 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Sun, 6 Jun 2021 10:10:55 -0400 Subject: [PATCH 0475/1621] 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 e10dfab2e85190328a3e6621d54b4adab1e72f9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Jun 2021 23:23:35 +0900 Subject: [PATCH 0476/1621] Ensure scorebar marker lookup is performed on the source the background is retrieved from --- osu.Game/Skinning/LegacyHealthDisplay.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index c601adc3a0..5a5c7f11ea 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -35,7 +35,10 @@ namespace osu.Game.Skinning { AutoSizeAxes = Axes.Both; - isNewStyle = getTexture(skin, "marker") != null; + var backgroundSource = skin.FindProvider(s => getTexture(s, "bg") != null); + + // the marker lookup to decide which display style must be performed on the source of the bg, which is the most common element. + isNewStyle = getTexture(backgroundSource, "marker") != null; // background implementation is the same for both versions. AddInternal(new Sprite { Texture = getTexture(skin, "bg") }); @@ -76,7 +79,7 @@ namespace osu.Game.Skinning protected override void Flash(JudgementResult result) => marker.Flash(result); - private static Texture getTexture(ISkinSource skin, string name) => skin.GetTexture($"scorebar-{name}"); + private static Texture getTexture(ISkin skin, string name) => skin?.GetTexture($"scorebar-{name}"); private static Color4 getFillColour(double hp) { From 888882ac63234f5372675218ea8449385f06bc33 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Sun, 6 Jun 2021 13:27:13 -0400 Subject: [PATCH 0477/1621] 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 0478/1621] 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 0479/1621] 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 0480/1621] 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 0481/1621] 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 166e4565be07b82a3ced961432053d969e90368f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 7 Jun 2021 13:59:17 +0900 Subject: [PATCH 0482/1621] Move `FruitVisualRepresentation` namespace --- .../Objects/Drawables/DrawableFruit.cs | 8 -------- .../Objects/FruitVisualRepresentation.cs | 13 +++++++++++++ .../Skinning/Default/FruitPiece.cs | 1 + .../Skinning/Default/FruitPulpFormation.cs | 2 +- .../Skinning/Legacy/LegacyFruitPiece.cs | 1 + 5 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Objects/FruitVisualRepresentation.cs diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index 0b89c46480..5b55036627 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -44,12 +44,4 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables ScalingContainer.RotateTo((RandomSingle(1) - 0.5f) * 40); } } - - public enum FruitVisualRepresentation - { - Pear, - Grape, - Pineapple, - Raspberry, - } } diff --git a/osu.Game.Rulesets.Catch/Objects/FruitVisualRepresentation.cs b/osu.Game.Rulesets.Catch/Objects/FruitVisualRepresentation.cs new file mode 100644 index 0000000000..7ec7050245 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/FruitVisualRepresentation.cs @@ -0,0 +1,13 @@ +// 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.Objects +{ + public enum FruitVisualRepresentation + { + Pear, + Grape, + Pineapple, + Raspberry, + } +} diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs index 49f128c960..14c94022f2 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs @@ -3,6 +3,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; namespace osu.Game.Rulesets.Catch.Skinning.Default diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPulpFormation.cs b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPulpFormation.cs index 88e0b5133a..f097361d2a 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPulpFormation.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPulpFormation.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.Objects; using osuTK; namespace osu.Game.Rulesets.Catch.Skinning.Default diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs index 969cc38e5b..bceb3bab42 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; namespace osu.Game.Rulesets.Catch.Skinning.Legacy From 29fa4fdf573176d68dda7996b8ccd6b5314caa0c Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Mon, 7 Jun 2021 01:08:39 -0400 Subject: [PATCH 0483/1621] 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 ac5c55bd2cf1c5e7bf565cef6c451a31894d54a3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 7 Jun 2021 14:49:37 +0900 Subject: [PATCH 0484/1621] Remove "fruit visual representation" state from `DrawableFruit` Instead, skin pieces compute visual representation from `IndexInBeatmap`. --- .../Objects/Drawables/CaughtFruit.cs | 13 +------------ .../Objects/Drawables/CaughtObject.cs | 2 ++ .../Objects/Drawables/DrawableFruit.cs | 10 +--------- .../Objects/Drawables/IHasCatchObjectState.cs | 2 ++ .../Objects/Drawables/IHasFruitState.cs | 15 --------------- osu.Game.Rulesets.Catch/Objects/Fruit.cs | 2 ++ .../Skinning/Default/CatchHitObjectPiece.cs | 2 ++ .../Skinning/Default/FruitPiece.cs | 7 ++++--- .../Skinning/Legacy/LegacyFruitPiece.cs | 12 ++++-------- .../Skinning/LegacyCatchHitObjectPiece.cs | 2 ++ 10 files changed, 20 insertions(+), 47 deletions(-) delete mode 100644 osu.Game.Rulesets.Catch/Objects/Drawables/IHasFruitState.cs diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs index 140b411c88..7c88090a20 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.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.Game.Rulesets.Catch.Skinning.Default; namespace osu.Game.Rulesets.Catch.Objects.Drawables @@ -9,21 +8,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables /// /// Represents a caught by the catcher. /// - public class CaughtFruit : CaughtObject, IHasFruitState + public class CaughtFruit : CaughtObject { - public Bindable VisualRepresentation { get; } = new Bindable(); - public CaughtFruit() : base(CatchSkinComponents.Fruit, _ => new FruitPiece()) { } - - public override void CopyStateFrom(IHasCatchObjectState objectState) - { - base.CopyStateFrom(objectState); - - var fruitState = (IHasFruitState)objectState; - VisualRepresentation.Value = fruitState.VisualRepresentation.Value; - } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs index 524505d588..d8bce9bb6d 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public PalpableCatchHitObject HitObject { get; private set; } public Bindable AccentColour { get; } = new Bindable(); public Bindable HyperDash { get; } = new Bindable(); + public Bindable IndexInBeatmap { get; } = new Bindable(); public Vector2 DisplaySize => Size * Scale; @@ -51,6 +52,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables Rotation = objectState.DisplayRotation; AccentColour.Value = objectState.AccentColour.Value; HyperDash.Value = objectState.HyperDash.Value; + IndexInBeatmap.Value = objectState.IndexInBeatmap.Value; } protected override void FreeAfterUse() diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index 5b55036627..0af7ee6c30 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -3,17 +3,14 @@ using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Skinning.Default; using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public class DrawableFruit : DrawablePalpableCatchHitObject, IHasFruitState + public class DrawableFruit : DrawablePalpableCatchHitObject { - public Bindable VisualRepresentation { get; } = new Bindable(); - public DrawableFruit() : this(null) { @@ -27,11 +24,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables [BackgroundDependencyLoader] private void load() { - IndexInBeatmap.BindValueChanged(change => - { - VisualRepresentation.Value = (FruitVisualRepresentation)(change.NewValue % 4); - }, true); - ScalingContainer.Child = new SkinnableDrawable( new CatchSkinComponent(CatchSkinComponents.Fruit), _ => new FruitPiece()); diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs index 81b61f0959..be0ee2821e 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables Bindable HyperDash { get; } + Bindable IndexInBeatmap { get; } + Vector2 DisplaySize { get; } float DisplayRotation { get; } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasFruitState.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasFruitState.cs deleted file mode 100644 index 2d4de543c3..0000000000 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasFruitState.cs +++ /dev/null @@ -1,15 +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.Bindables; - -namespace osu.Game.Rulesets.Catch.Objects.Drawables -{ - /// - /// Provides a visual state of a . - /// - public interface IHasFruitState : IHasCatchObjectState - { - Bindable VisualRepresentation { get; } - } -} diff --git a/osu.Game.Rulesets.Catch/Objects/Fruit.cs b/osu.Game.Rulesets.Catch/Objects/Fruit.cs index 43486796ad..4818fe2cad 100644 --- a/osu.Game.Rulesets.Catch/Objects/Fruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Fruit.cs @@ -9,5 +9,7 @@ namespace osu.Game.Rulesets.Catch.Objects public class Fruit : PalpableCatchHitObject { public override Judgement CreateJudgement() => new CatchJudgement(); + + public static FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => (FruitVisualRepresentation)(indexInBeatmap % 4); } } diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs index 51c06c8e37..2db3bae034 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default { public readonly Bindable AccentColour = new Bindable(); public readonly Bindable HyperDash = new Bindable(); + public readonly Bindable IndexInBeatmap = new Bindable(); [Resolved] protected IHasCatchObjectState ObjectState { get; private set; } @@ -37,6 +38,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default AccentColour.BindTo(ObjectState.AccentColour); HyperDash.BindTo(ObjectState.HyperDash); + IndexInBeatmap.BindTo(ObjectState.IndexInBeatmap); HyperDash.BindValueChanged(hyper => { diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs index 14c94022f2..cfe0df0c97 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs @@ -4,7 +4,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawables; namespace osu.Game.Rulesets.Catch.Skinning.Default { @@ -40,8 +39,10 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default { base.LoadComplete(); - var fruitState = (IHasFruitState)ObjectState; - VisualRepresentation.BindTo(fruitState.VisualRepresentation); + IndexInBeatmap.BindValueChanged(index => + { + VisualRepresentation.Value = Fruit.GetVisualRepresentation(index.NewValue); + }, true); } } } diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs index bceb3bab42..f002bab219 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs @@ -1,24 +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.Framework.Bindables; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawables; namespace osu.Game.Rulesets.Catch.Skinning.Legacy { internal class LegacyFruitPiece : LegacyCatchHitObjectPiece { - public readonly Bindable VisualRepresentation = new Bindable(); - protected override void LoadComplete() { base.LoadComplete(); - var fruitState = (IHasFruitState)ObjectState; - VisualRepresentation.BindTo(fruitState.VisualRepresentation); - - VisualRepresentation.BindValueChanged(visual => setTexture(visual.NewValue), true); + IndexInBeatmap.BindValueChanged(index => + { + setTexture(Fruit.GetVisualRepresentation(index.NewValue)); + }, true); } private void setTexture(FruitVisualRepresentation visualRepresentation) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs index 4b1f5a4724..8c1ba014cf 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Catch.Skinning { public readonly Bindable AccentColour = new Bindable(); public readonly Bindable HyperDash = new Bindable(); + public readonly Bindable IndexInBeatmap = new Bindable(); private readonly Sprite colouredSprite; private readonly Sprite overlaySprite; @@ -64,6 +65,7 @@ namespace osu.Game.Rulesets.Catch.Skinning AccentColour.BindTo(ObjectState.AccentColour); HyperDash.BindTo(ObjectState.HyperDash); + IndexInBeatmap.BindTo(ObjectState.IndexInBeatmap); hyperSprite.Colour = Skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ?? Skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? From bb02c35f2de27409182ca79270f7744da282b356 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 7 Jun 2021 15:10:47 +0900 Subject: [PATCH 0485/1621] Move all osu!catch legacy skin piece files to the correct location --- .../Skinning/{ => Legacy}/LegacyBananaPiece.cs | 2 +- .../Skinning/{ => Legacy}/LegacyCatchHitObjectPiece.cs | 2 +- .../Skinning/{ => Legacy}/LegacyDropletPiece.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game.Rulesets.Catch/Skinning/{ => Legacy}/LegacyBananaPiece.cs (91%) rename osu.Game.Rulesets.Catch/Skinning/{ => Legacy}/LegacyCatchHitObjectPiece.cs (98%) rename osu.Game.Rulesets.Catch/Skinning/{ => Legacy}/LegacyDropletPiece.cs (93%) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyBananaPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyBananaPiece.cs similarity index 91% rename from osu.Game.Rulesets.Catch/Skinning/LegacyBananaPiece.cs rename to osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyBananaPiece.cs index f80e50c8c0..5bd5b0d4bb 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyBananaPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyBananaPiece.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics.Textures; -namespace osu.Game.Rulesets.Catch.Skinning +namespace osu.Game.Rulesets.Catch.Skinning.Legacy { public class LegacyBananaPiece : LegacyCatchHitObjectPiece { diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchHitObjectPiece.cs similarity index 98% rename from osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs rename to osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchHitObjectPiece.cs index 4b1f5a4724..2e772df551 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchHitObjectPiece.cs @@ -13,7 +13,7 @@ using osu.Game.Skinning; using osuTK; using osuTK.Graphics; -namespace osu.Game.Rulesets.Catch.Skinning +namespace osu.Game.Rulesets.Catch.Skinning.Legacy { public abstract class LegacyCatchHitObjectPiece : PoolableDrawable { diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyDropletPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs similarity index 93% rename from osu.Game.Rulesets.Catch/Skinning/LegacyDropletPiece.cs rename to osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs index 8f4331d2a3..2c5cbe1e41 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyDropletPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics.Textures; using osuTK; -namespace osu.Game.Rulesets.Catch.Skinning +namespace osu.Game.Rulesets.Catch.Skinning.Legacy { public class LegacyDropletPiece : LegacyCatchHitObjectPiece { From 6c9594ee350a1710cc9a9eb783a8a9bad27feff6 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Mon, 7 Jun 2021 02:40:15 -0400 Subject: [PATCH 0486/1621] 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 aa700702fe730763aa1630985e1ab35922501332 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Jun 2021 15:48:45 +0900 Subject: [PATCH 0487/1621] 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 e95c7e6619..1216b1772f 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 49b86ad56e..21a890014a 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 cbb6a21fd1..bf080e4def 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 277eb9fa6ed825c53ccefe87e641e186ff998bd3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Jun 2021 15:58:41 +0900 Subject: [PATCH 0488/1621] Fix slider repeat arrow not updating rotation immediately while paused in editor A bit of a local solution, but not sure there's a better way to handle this. Closes #13342. --- .../Objects/Drawables/DrawableSliderRepeat.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index b7458b5695..4a2a18ffd6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -152,7 +152,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables while (Math.Abs(aimRotation - Arrow.Rotation) > 180) aimRotation += aimRotation < Arrow.Rotation ? 360 : -360; - if (!hasRotation) + // The clock may be paused in a scenario like the editor. + if (!hasRotation || !Clock.IsRunning) { Arrow.Rotation = aimRotation; hasRotation = true; From e8d41477731d41e2aa0adfc6a688ac131cc54785 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Jun 2021 16:08:44 +0900 Subject: [PATCH 0489/1621] Add missing null handling for never `Markdig` version --- .../Graphics/Containers/Markdown/OsuMarkdownContainer.cs | 7 +++++-- osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 6facf4e26c..81f30bd406 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -30,9 +30,12 @@ namespace osu.Game.Graphics.Containers.Markdown break; case ListItemBlock listItemBlock: - var isOrdered = ((ListBlock)listItemBlock.Parent).IsOrdered; - var childContainer = CreateListItem(listItemBlock, level, isOrdered); + bool isOrdered = ((ListBlock)listItemBlock.Parent)?.IsOrdered == true; + + OsuMarkdownListItem childContainer = CreateListItem(listItemBlock, level, isOrdered); + container.Add(childContainer); + foreach (var single in listItemBlock) base.AddMarkdownComponent(single, childContainer.Content, level); break; diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index acaaa523a2..6f0b433acb 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Wiki.Markdown case ParagraphBlock paragraphBlock: // Check if paragraph only contains an image - if (paragraphBlock.Inline.Count() == 1 && paragraphBlock.Inline.FirstChild is LinkInline { IsImage: true } linkInline) + if (paragraphBlock.Inline?.Count() == 1 && paragraphBlock.Inline.FirstChild is LinkInline { IsImage: true } linkInline) { container.Add(new WikiMarkdownImageBlock(linkInline)); return; From f677f9b5f44a36fd4dc880c55bd7c998fb5b98db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Jun 2021 17:22:30 +0900 Subject: [PATCH 0490/1621] Stop `BackgroundScreenDefault` from reloading beatmap background when already correct --- .../TestSceneBackgroundScreenDefault.cs | 59 ++++++++++ .../Backgrounds/BackgroundScreenDefault.cs | 103 ++++++++++-------- 2 files changed, 115 insertions(+), 47 deletions(-) create mode 100644 osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs new file mode 100644 index 0000000000..ef50f866d5 --- /dev/null +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -0,0 +1,59 @@ +// 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.Screens; +using osu.Framework.Testing; +using osu.Game.Configuration; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Online.API; +using osu.Game.Screens; +using osu.Game.Screens.Backgrounds; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Background +{ + [TestFixture] + public class TestSceneBackgroundScreenDefault : OsuTestScene + { + private BackgroundScreenStack stack; + private BackgroundScreenDefault screen; + + private Graphics.Backgrounds.Background getCurrentBackground() => screen.ChildrenOfType().FirstOrDefault(); + + [Resolved] + private OsuConfigManager config { get; set; } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create background stack", () => Child = stack = new BackgroundScreenStack()); + AddStep("push default screen", () => stack.Push(screen = new BackgroundScreenDefault(false))); + AddUntilStep("wait for screen to load", () => screen.IsCurrentScreen()); + } + + [Test] + public void TestBeatmapDoesntReloadOnNoChange() + { + BeatmapBackground last = null; + + setSourceMode(BackgroundSource.Beatmap); + setSupporter(true); + + AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground() as BeatmapBackground) != null); + AddAssert("next doesn't load new background", () => screen.Next() == false); + + // doesn't really need to be checked but might as well. + AddWaitStep("wait a bit", 5); + AddUntilStep("ensure same background instance", () => last == getCurrentBackground()); + } + + private void setSourceMode(BackgroundSource source) => + AddStep("set background mode to beatmap", () => config.SetValue(OsuSetting.MenuBackgroundSource, source)); + + private void setSupporter(bool isSupporter) => + AddStep("set supporter", () => ((DummyAPIAccess)API).LocalUser.Value = new User { IsSupporter = isSupporter }); + } +} diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index b02e7ddb0d..dc27514459 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -5,8 +5,8 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Backgrounds; @@ -53,14 +53,41 @@ namespace osu.Game.Screens.Backgrounds mode.ValueChanged += _ => Next(); beatmap.ValueChanged += _ => Next(); introSequence.ValueChanged += _ => Next(); - seasonalBackgroundLoader.SeasonalBackgroundChanged += Next; + seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Next(); currentDisplay = RNG.Next(0, background_count); Next(); } - private void display(Background newBackground) + private ScheduledDelegate nextTask; + private CancellationTokenSource cancellationTokenSource; + + /// + /// Request loading the next background. + /// + /// Whether a new background was queued for load. May return false if the current background is still valid. + public bool Next() + { + var nextBackground = createBackground(); + + // in the case that the background hasn't changed, we want to avoid cancelling any tasks that could still be loading. + if (nextBackground == background) + return false; + + cancellationTokenSource?.Cancel(); + cancellationTokenSource = new CancellationTokenSource(); + + nextTask?.Cancel(); + nextTask = Scheduler.AddDelayed(() => + { + LoadComponentAsync(nextBackground, displayNext, cancellationTokenSource.Token); + }, 100); + + return true; + } + + private void displayNext(Background newBackground) { background?.FadeOut(800, Easing.InOutSine); background?.Expire(); @@ -69,68 +96,50 @@ namespace osu.Game.Screens.Backgrounds currentDisplay++; } - private ScheduledDelegate nextTask; - private CancellationTokenSource cancellationTokenSource; - - public void Next() - { - nextTask?.Cancel(); - cancellationTokenSource?.Cancel(); - cancellationTokenSource = new CancellationTokenSource(); - nextTask = Scheduler.AddDelayed(() => LoadComponentAsync(createBackground(), display, cancellationTokenSource.Token), 100); - } - private Background createBackground() { - Background newBackground; - string backgroundName; + // seasonal background loading gets highest priority. + Background newBackground = seasonalBackgroundLoader.LoadNextBackground(); - var seasonalBackground = seasonalBackgroundLoader.LoadNextBackground(); - - if (seasonalBackground != null) - { - seasonalBackground.Depth = currentDisplay; - return seasonalBackground; - } - - switch (introSequence.Value) - { - case IntroSequence.Welcome: - backgroundName = "Intro/Welcome/menu-background"; - break; - - default: - backgroundName = $@"Menu/menu-background-{currentDisplay % background_count + 1}"; - break; - } - - if (user.Value?.IsSupporter ?? false) + if (newBackground == null && user.Value?.IsSupporter == true) { switch (mode.Value) { case BackgroundSource.Beatmap: - newBackground = new BeatmapBackground(beatmap.Value, backgroundName); - break; - case BackgroundSource.BeatmapWithStoryboard: - newBackground = AllowStoryboardBackground - ? new BeatmapBackgroundWithStoryboard(beatmap.Value, backgroundName) - : new BeatmapBackground(beatmap.Value, backgroundName); - break; + { + // this method is called in many cases where the beatmap hasn't changed (ie. on screen transitions). + // if a background is already displayed for the requested beatmap, we don't want to load it again. + if ((background as BeatmapBackground)?.Beatmap == beatmap.Value) + return background; - default: - newBackground = new SkinnedBackground(skin.Value, backgroundName); + if (mode.Value == BackgroundSource.BeatmapWithStoryboard && AllowStoryboardBackground) + newBackground = new BeatmapBackgroundWithStoryboard(beatmap.Value, getBackgroundTextureName()); + + newBackground ??= new BeatmapBackground(beatmap.Value, getBackgroundTextureName()); break; + } } } - else - newBackground = new Background(backgroundName); + newBackground ??= new Background(getBackgroundTextureName()); newBackground.Depth = currentDisplay; return newBackground; } + private string getBackgroundTextureName() + { + switch (introSequence.Value) + { + case IntroSequence.Welcome: + return @"Intro/Welcome/menu-background"; + + default: + return $@"Menu/menu-background-{currentDisplay % background_count + 1}"; + } + } + private class SkinnedBackground : Background { private readonly Skin skin; From 59130be99cce706979294681a0aa2cd0f159c7c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Jun 2021 17:32:04 +0900 Subject: [PATCH 0491/1621] Fix switching storyboard mode not triggering a reload --- .../Screens/Backgrounds/BackgroundScreenDefault.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index dc27514459..6bcfaac907 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -108,15 +108,16 @@ namespace osu.Game.Screens.Backgrounds case BackgroundSource.Beatmap: case BackgroundSource.BeatmapWithStoryboard: { - // this method is called in many cases where the beatmap hasn't changed (ie. on screen transitions). - // if a background is already displayed for the requested beatmap, we don't want to load it again. - if ((background as BeatmapBackground)?.Beatmap == beatmap.Value) - return background; - if (mode.Value == BackgroundSource.BeatmapWithStoryboard && AllowStoryboardBackground) newBackground = new BeatmapBackgroundWithStoryboard(beatmap.Value, getBackgroundTextureName()); - newBackground ??= new BeatmapBackground(beatmap.Value, getBackgroundTextureName()); + + // this method is called in many cases where the beatmap hasn't changed (ie. on screen transitions). + // if a background is already displayed for the requested beatmap, we don't want to load it again. + if (background?.GetType() == newBackground.GetType() && + (background as BeatmapBackground)?.Beatmap == beatmap.Value) + return background; + break; } } From 729e05241f22e096682e550ae6ab502e32b58c2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Jun 2021 17:32:10 +0900 Subject: [PATCH 0492/1621] Add more test coverage --- .../TestSceneBackgroundScreenDefault.cs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index ef50f866d5..09fe9b3767 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -34,6 +34,33 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("wait for screen to load", () => screen.IsCurrentScreen()); } + [Test] + public void TestTogglingStoryboardSwitchesBackgroundType() + { + setSupporter(true); + + setSourceMode(BackgroundSource.Beatmap); + AddUntilStep("is beatmap background", () => getCurrentBackground() is BeatmapBackground); + + setSourceMode(BackgroundSource.BeatmapWithStoryboard); + AddUntilStep("is storyboard background", () => getCurrentBackground() is BeatmapBackgroundWithStoryboard); + } + + [Test] + public void TestTogglingSupporterTogglesBeatmapBackground() + { + setSourceMode(BackgroundSource.Beatmap); + + setSupporter(true); + AddUntilStep("is beatmap background", () => getCurrentBackground() is BeatmapBackground); + + setSupporter(false); + AddUntilStep("is default background", () => !(getCurrentBackground() is BeatmapBackground)); + + setSupporter(true); + AddUntilStep("is beatmap background", () => getCurrentBackground() is BeatmapBackground); + } + [Test] public void TestBeatmapDoesntReloadOnNoChange() { @@ -54,6 +81,10 @@ namespace osu.Game.Tests.Visual.Background AddStep("set background mode to beatmap", () => config.SetValue(OsuSetting.MenuBackgroundSource, source)); private void setSupporter(bool isSupporter) => - AddStep("set supporter", () => ((DummyAPIAccess)API).LocalUser.Value = new User { IsSupporter = isSupporter }); + AddStep($"set supporter {isSupporter}", () => ((DummyAPIAccess)API).LocalUser.Value = new User + { + IsSupporter = isSupporter, + Id = API.LocalUser.Value.Id + 1, + }); } } From e606bf249afa83a73ebd9924886bced36afd9222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Jun 2021 11:05:21 +0200 Subject: [PATCH 0493/1621] Move dependency specification to BDL As it is not used anywhere else. --- osu.Game/Overlays/Wiki/WikiTableOfContents.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiTableOfContents.cs b/osu.Game/Overlays/Wiki/WikiTableOfContents.cs index 77441c26ed..c0615dce1f 100644 --- a/osu.Game/Overlays/Wiki/WikiTableOfContents.cs +++ b/osu.Game/Overlays/Wiki/WikiTableOfContents.cs @@ -54,9 +54,6 @@ namespace osu.Game.Overlays.Wiki private class TableOfContentsEntry : OsuHoverContainer { - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - private readonly MarkdownHeading target; private readonly OsuTextFlowContainer textFlow; @@ -83,7 +80,7 @@ namespace osu.Game.Overlays.Wiki protected override IEnumerable EffectTargets => new Drawable[] { textFlow }; [BackgroundDependencyLoader] - private void load(OverlayScrollContainer scrollContainer) + private void load(OverlayColourProvider colourProvider, OverlayScrollContainer scrollContainer) { IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; From 65f594f860b5e0be75e0c4b4eb09d50e859b6ba8 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Mon, 7 Jun 2021 05:08:18 -0400 Subject: [PATCH 0494/1621] 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 a0bda9ad595e48aabe2141359e2c38fdc3822188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Jun 2021 11:18:18 +0200 Subject: [PATCH 0495/1621] Hoist scroll cache declaration to original place of definition --- osu.Game/Overlays/OnlineOverlay.cs | 3 +++ osu.Game/Overlays/WikiOverlay.cs | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index de33e4a1bc..a610511398 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -13,7 +14,9 @@ namespace osu.Game.Overlays { protected override Container Content => content; + [Cached] protected readonly OverlayScrollContainer ScrollFlow; + protected readonly LoadingLayer Loading; private readonly Container content; diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index fc24820f3c..bde73b6180 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -25,9 +25,6 @@ namespace osu.Game.Overlays [Resolved] private IAPIProvider api { get; set; } - [Cached] - private readonly OverlayScrollContainer scrollContainer; - private GetWikiRequest request; private CancellationTokenSource cancellationToken; @@ -39,7 +36,6 @@ namespace osu.Game.Overlays public WikiOverlay() : base(OverlayColourScheme.Orange, false) { - scrollContainer = ScrollFlow; } public void ShowPage(string pagePath = index_path) From a0fbf29b987da2eead9141b45bf0e7c4fd6c8d68 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Mon, 7 Jun 2021 11:24:48 +0200 Subject: [PATCH 0496/1621] add `application/x-osu-archive` mime type to Android `IntentFilter`s --- osu.Android/OsuGameActivity.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index cffcea22c2..063e02d349 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -20,7 +20,8 @@ namespace osu.Android [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] - [IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed" })] + [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-archive")] + [IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed", "application/x-osu-archive" })] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] public class OsuGameActivity : AndroidGameActivity { From 122a624b7fb68d8f5bb55ad30b96cd38be8e9a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Jun 2021 12:15:37 +0200 Subject: [PATCH 0497/1621] Remove bogus `CatchHitWindows` `CatchHitWindows` were a vestige from the past, and were not actually used anywhere except for the hit error meter test, giving off an appearance that the hit error meter was working properly. `CatchHitObject` actually specifies empty hit windows. --- .../Scoring/CatchHitWindows.cs | 22 ------------------- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 6 ++--- 2 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs deleted file mode 100644 index 0a444d923e..0000000000 --- a/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs +++ /dev/null @@ -1,22 +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.Scoring; - -namespace osu.Game.Rulesets.Catch.Scoring -{ - public class CatchHitWindows : HitWindows - { - public override bool IsHitResultAllowed(HitResult result) - { - switch (result) - { - case HitResult.Great: - case HitResult.Miss: - return true; - } - - return false; - } - } -} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index ab13095af4..acc7287b5a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mods; @@ -84,10 +83,9 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestCatch() + public void TestEmpty() { - AddStep("OD 1", () => recreateDisplay(new CatchHitWindows(), 1)); - AddStep("OD 10", () => recreateDisplay(new CatchHitWindows(), 10)); + AddStep("empty windows", () => recreateDisplay(HitWindows.Empty, 5)); } private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) From 37d062c7cd736563d2760ac5a9f4b154a74c1c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Jun 2021 12:25:48 +0200 Subject: [PATCH 0498/1621] Add failing assertions to hit error meter test --- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 18 ++++++++++++++++-- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 2 +- .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index acc7287b5a..d2e25d1f9f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Graphics.Sprites; @@ -86,6 +88,18 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestEmpty() { AddStep("empty windows", () => recreateDisplay(HitWindows.Empty, 5)); + + AddStep("hit", () => newJudgement()); + AddAssert("no bars added", () => !this.ChildrenOfType().Any()); + AddAssert("circle added", () => + this.ChildrenOfType().All( + meter => meter.ChildrenOfType().Count() == 1)); + + AddStep("miss", () => newJudgement(50, HitResult.Miss)); + AddAssert("no bars added", () => !this.ChildrenOfType().Any()); + AddAssert("circle added", () => + this.ChildrenOfType().All( + meter => meter.ChildrenOfType().Count() == 2)); } private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) @@ -152,12 +166,12 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - private void newJudgement(double offset = 0) + private void newJudgement(double offset = 0, HitResult result = HitResult.Perfect) { scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = drawableRuleset.HitWindows }, new Judgement()) { TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, - Type = HitResult.Perfect, + Type = result, }); } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 5d0263772d..5a8a65acfd 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -244,7 +244,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private float getRelativeJudgementPosition(double value) => Math.Clamp((float)((value / maxHitWindow) + 1) / 2, 0, 1); - private class JudgementLine : CompositeDrawable + internal class JudgementLine : CompositeDrawable { private const int judgement_fade_duration = 5000; diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index e9ccbcdae2..86c0de8855 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters } } - private class HitErrorCircle : Container + internal class HitErrorCircle : Container { public bool IsRemoved { get; private set; } From 0531c2dcd9a5072f684541f039f8a1dfd1a1468e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Jun 2021 12:31:24 +0200 Subject: [PATCH 0499/1621] Move empty window check to bar error meter It's not valid in the base `HitErrorMeter`, as the colour meter only displays colour for a given judgement, so it is still valid to add new items to it even if the hit window is 0, as misses are still possible. --- .../Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 2 +- .../Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 5a8a65acfd..0412085d1d 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -214,7 +214,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected override void OnNewJudgement(JudgementResult judgement) { - if (!judgement.IsHit) + if (!judgement.IsHit || judgement.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0) return; if (judgementsContainer.Count > max_concurrent_judgements) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index b0f9928b13..17a6e772fb 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -32,15 +32,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { base.LoadComplete(); - processor.NewJudgement += onNewJudgement; - } - - private void onNewJudgement(JudgementResult result) - { - if (result.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0) - return; - - OnNewJudgement(result); + processor.NewJudgement += OnNewJudgement; } protected abstract void OnNewJudgement(JudgementResult judgement); @@ -74,7 +66,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters base.Dispose(isDisposing); if (processor != null) - processor.NewJudgement -= onNewJudgement; + processor.NewJudgement -= OnNewJudgement; } } } From 1b4771655aa694dabdfdb9a78e3d063e32d34b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Jun 2021 13:13:24 +0200 Subject: [PATCH 0500/1621] Adjust test scene to avoid cross-test interference * Move steps from ctor to a separate basic test. * Wait for barrage to complete in basic test, as not doing so polluted state of other tests. * Reset score processor after every test. --- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index d2e25d1f9f..2a12577ad8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -30,15 +30,22 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneHitErrorMeter : OsuTestScene { - [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(); + [Cached(typeof(ScoreProcessor))] + private TestScoreProcessor scoreProcessor = new TestScoreProcessor(); [Cached(typeof(DrawableRuleset))] private TestDrawableRuleset drawableRuleset = new TestDrawableRuleset(); - public TestSceneHitErrorMeter() + [SetUpSteps] + public void SetUp() { - recreateDisplay(new OsuHitWindows(), 5); + AddStep("reset score processor", () => scoreProcessor.Reset()); + } + + [Test] + public void TestBasic() + { + AddStep("create display", () => recreateDisplay(new OsuHitWindows(), 5)); AddRepeatStep("New random judgement", () => newJudgement(), 40); @@ -46,12 +53,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddRepeatStep("New max positive", () => newJudgement(drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20); AddStep("New fixed judgement (50ms)", () => newJudgement(50)); + ScheduledDelegate del = null; AddStep("Judgement barrage", () => { int runCount = 0; - ScheduledDelegate del = null; - del = Scheduler.AddDelayed(() => { newJudgement(runCount++ / 10f); @@ -61,6 +67,7 @@ namespace osu.Game.Tests.Visual.Gameplay del?.Cancel(); }, 10, true); }); + AddUntilStep("wait for barrage", () => del.Cancelled); } [Test] @@ -211,5 +218,10 @@ namespace osu.Game.Tests.Visual.Gameplay public override void CancelResume() => throw new NotImplementedException(); } + + private class TestScoreProcessor : ScoreProcessor + { + public void Reset() => base.Reset(false); + } } } From 08701b5eab700ea2944cf358cd60616d8fe9c96a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Jun 2021 23:23:10 +0900 Subject: [PATCH 0501/1621] Ensure all lookups in `LegacyHealthDisplay` use the found provider Not actually needed to fix the remaining issue but does feel better --- osu.Game/Skinning/LegacyHealthDisplay.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 5a5c7f11ea..9d3bafd0b1 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -20,9 +20,6 @@ namespace osu.Game.Skinning { private const double epic_cutoff = 0.5; - [Resolved] - private ISkinSource skin { get; set; } - private LegacyHealthPiece fill; private LegacyHealthPiece marker; @@ -31,14 +28,14 @@ namespace osu.Game.Skinning private bool isNewStyle; [BackgroundDependencyLoader] - private void load() + private void load(ISkinSource source) { AutoSizeAxes = Axes.Both; - var backgroundSource = skin.FindProvider(s => getTexture(s, "bg") != null); + var skin = source.FindProvider(s => getTexture(s, "bg") != null); // the marker lookup to decide which display style must be performed on the source of the bg, which is the most common element. - isNewStyle = getTexture(backgroundSource, "marker") != null; + isNewStyle = getTexture(skin, "marker") != null; // background implementation is the same for both versions. AddInternal(new Sprite { Texture = getTexture(skin, "bg") }); @@ -98,7 +95,7 @@ namespace osu.Game.Skinning private readonly Texture dangerTexture; private readonly Texture superDangerTexture; - public LegacyOldStyleMarker(ISkinSource skin) + public LegacyOldStyleMarker(ISkin skin) { normalTexture = getTexture(skin, "ki"); dangerTexture = getTexture(skin, "kidanger"); @@ -129,9 +126,9 @@ namespace osu.Game.Skinning public class LegacyNewStyleMarker : LegacyMarker { - private readonly ISkinSource skin; + private readonly ISkin skin; - public LegacyNewStyleMarker(ISkinSource skin) + public LegacyNewStyleMarker(ISkin skin) { this.skin = skin; } @@ -153,7 +150,7 @@ namespace osu.Game.Skinning internal class LegacyOldStyleFill : LegacyHealthPiece { - public LegacyOldStyleFill(ISkinSource skin) + public LegacyOldStyleFill(ISkin skin) { // required for sizing correctly.. var firstFrame = getTexture(skin, "colour-0"); @@ -176,7 +173,7 @@ namespace osu.Game.Skinning internal class LegacyNewStyleFill : LegacyHealthPiece { - public LegacyNewStyleFill(ISkinSource skin) + public LegacyNewStyleFill(ISkin skin) { InternalChild = new Sprite { From c0305343bc9312333957362d124c906f2febd82b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Jun 2021 23:23:44 +0900 Subject: [PATCH 0502/1621] Fix `FindProvider` incorrectly returning `LegacySkinTransformer` itself --- osu.Game/Skinning/LegacySkinTransformer.cs | 8 +++++++- osu.Game/Skinning/SkinProvidingContainer.cs | 12 ++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index cace4acf6c..651fdddb1b 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Skinning /// /// Transformer used to handle support of legacy features for individual rulesets. /// - public abstract class LegacySkinTransformer : ISkin + public abstract class LegacySkinTransformer : ISkinSource { /// /// Source of the which is being transformed. @@ -50,5 +50,11 @@ namespace osu.Game.Skinning public abstract IBindable GetConfig(TLookup lookup); public ISkin FindProvider(Func lookupFunction) => Source.FindProvider(lookupFunction); + + public event Action SourceChanged + { + add { throw new NotSupportedException(); } + remove { } + } } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 863b5f5a24..0e16cf43ee 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -46,8 +46,16 @@ namespace osu.Game.Skinning public ISkin FindProvider(Func lookupFunction) { - if (skin != null && lookupFunction(skin)) - return skin; + if (skin is ISkinSource source) + { + if (source.FindProvider(lookupFunction) is ISkin found) + return found; + } + else if (skin != null) + { + if (lookupFunction(skin)) + return skin; + } return fallbackSource?.FindProvider(lookupFunction); } From 6d56e02ddbe825599c3aaf1c09310c08858c8158 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 00:02:57 +0900 Subject: [PATCH 0503/1621] Add back incorrectly reverted animation handling logic This reverts commit b904fa6615ad210afc94e874a3861572e9bb0499. --- osu.Game/Skinning/LegacySkinExtensions.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index d8fb1fa664..051bcf4a8e 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -54,9 +54,16 @@ namespace osu.Game.Skinning IEnumerable getTextures() { + ISkin lookupSource = null; + for (int i = 0; true; i++) { - if ((texture = source.GetTexture($"{componentName}{animationSeparator}{i}", wrapModeS, wrapModeT)) == null) + string frameName = $"{componentName}{animationSeparator}{i}"; + + // ensure all textures are retrieved from the same skin source. + lookupSource ??= (source as ISkinSource)?.FindProvider(s => s.GetTexture(frameName, wrapModeS, wrapModeT) != null) ?? source; + + if ((texture = lookupSource?.GetTexture(frameName, wrapModeS, wrapModeT)) == null) break; yield return texture; From 273d66a0e0a76ca45e5188601bd03a34e9804011 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 00:42:34 +0900 Subject: [PATCH 0504/1621] Fix `TaikoMascot` texture animation lookups --- osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs index 9c76aea54c..3706acbe23 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs @@ -85,8 +85,12 @@ namespace osu.Game.Rulesets.Taiko.UI } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load(ISkinSource source) { + ISkin skin = source.FindProvider(s => getAnimationFrame(s, state, 0) != null); + + if (skin == null) return; + for (int frameIndex = 0; true; frameIndex++) { var texture = getAnimationFrame(skin, state, frameIndex); @@ -112,8 +116,12 @@ namespace osu.Game.Rulesets.Taiko.UI } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load(ISkinSource source) { + ISkin skin = source.FindProvider(s => getAnimationFrame(s, TaikoMascotAnimationState.Clear, 0) != null); + + if (skin == null) return; + foreach (var frameIndex in clear_animation_sequence) { var texture = getAnimationFrame(skin, TaikoMascotAnimationState.Clear, frameIndex); From e7e9197f03d2247fb2dfbb599401dff62a8160ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 00:42:50 +0900 Subject: [PATCH 0505/1621] Fix `FindProvider` not correctly checking legacy default in `SkinManager` --- osu.Game/Skinning/SkinManager.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 9aa2d90064..ea4e1232c3 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -48,6 +48,8 @@ namespace osu.Game.Skinning protected override string ImportFromStablePath => "Skins"; + private readonly Skin defaultLegacySkin; + public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, IResourceStore resources, AudioManager audio) : base(storage, contextFactory, new SkinStore(contextFactory, storage), host) { @@ -55,6 +57,8 @@ namespace osu.Game.Skinning this.host = host; this.resources = resources; + defaultLegacySkin = new DefaultLegacySkin(this); + CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue); CurrentSkin.ValueChanged += skin => { @@ -212,9 +216,16 @@ namespace osu.Game.Skinning public IBindable GetConfig(TLookup lookup) => lookupWithFallback(s => s.GetConfig(lookup)); - public ISkin FindProvider(Func lookupFunction) => lookupFunction(CurrentSkin.Value) ? CurrentSkin.Value : null; + public ISkin FindProvider(Func lookupFunction) + { + if (lookupFunction(CurrentSkin.Value)) + return CurrentSkin.Value; - private Skin defaultLegacySkin; + if (CurrentSkin.Value is LegacySkin && lookupFunction(defaultLegacySkin)) + return defaultLegacySkin; + + return null; + } private T lookupWithFallback(Func func) where T : class @@ -224,8 +235,6 @@ namespace osu.Game.Skinning if (selectedSkin != null) return selectedSkin; - defaultLegacySkin ??= new DefaultLegacySkin(this); - if (CurrentSkin.Value is LegacySkin) return func(defaultLegacySkin); From 2c1f22d7ae914e84a6263d4ae7231d2f0f2a3bb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 01:17:20 +0900 Subject: [PATCH 0506/1621] Refactor animation lookup to properly handle skins providing non-animated resources --- osu.Game/Skinning/LegacySkinExtensions.cs | 27 ++++++++++++++--------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 051bcf4a8e..ec25268be4 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -27,6 +27,18 @@ namespace osu.Game.Skinning { Texture texture; + // find the first source which provides either the animated or non-animated version. + ISkin skin = (source as ISkinSource)?.FindProvider(s => + { + if (animatable && s.GetTexture(getFrameName(0)) != null) + return true; + + return s.GetTexture(componentName, wrapModeS, wrapModeT) != null; + }) ?? source; + + if (skin == null) + return null; + if (animatable) { var textures = getTextures().ToArray(); @@ -35,7 +47,7 @@ namespace osu.Game.Skinning { var animation = new SkinnableTextureAnimation(startAtCurrentTime) { - DefaultFrameLength = frameLength ?? getFrameLength(source, applyConfigFrameRate, textures), + DefaultFrameLength = frameLength ?? getFrameLength(skin, applyConfigFrameRate, textures), Loop = looping, }; @@ -47,28 +59,23 @@ namespace osu.Game.Skinning } // if an animation was not allowed or not found, fall back to a sprite retrieval. - if ((texture = source.GetTexture(componentName, wrapModeS, wrapModeT)) != null) + if ((texture = skin.GetTexture(componentName, wrapModeS, wrapModeT)) != null) return new Sprite { Texture = texture }; return null; IEnumerable getTextures() { - ISkin lookupSource = null; - for (int i = 0; true; i++) { - string frameName = $"{componentName}{animationSeparator}{i}"; - - // ensure all textures are retrieved from the same skin source. - lookupSource ??= (source as ISkinSource)?.FindProvider(s => s.GetTexture(frameName, wrapModeS, wrapModeT) != null) ?? source; - - if ((texture = lookupSource?.GetTexture(frameName, wrapModeS, wrapModeT)) == null) + if ((texture = skin.GetTexture(getFrameName(i), wrapModeS, wrapModeT)) == null) break; yield return texture; } } + + string getFrameName(int frameIndex) => $"{componentName}{animationSeparator}{frameIndex}"; } public static bool HasFont(this ISkin source, LegacyFont font) From 06840d78cc4df29d7b5f8a58c0b7cdb44f83a9ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 12:04:29 +0900 Subject: [PATCH 0507/1621] Remove now unused method --- osu.Game/Skinning/DefaultLegacySkin.cs | 2 -- osu.Game/Skinning/LegacyBeatmapSkin.cs | 8 -------- osu.Game/Skinning/LegacySkin.cs | 3 --- 3 files changed, 13 deletions(-) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 1d17b5ce20..30192182f3 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -31,8 +31,6 @@ namespace osu.Game.Skinning Configuration.LegacyVersion = 2.7m; } - protected override DefaultLegacySkin CreateFallbackSkin(IResourceStore storage, IStorageResourceProvider resources) => null; - public static SkinInfo Info { get; } = new SkinInfo { ID = SkinInfo.CLASSIC_SKIN, // this is temporary until database storage is decided upon. diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 2374cb976b..caf37e5bc9 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -70,14 +70,6 @@ namespace osu.Game.Skinning return base.GetSample(sampleInfo); } - protected override DefaultLegacySkin CreateFallbackSkin(IResourceStore storage, IStorageResourceProvider resources) - { - // for simplicity, beatmap skins don't do lookups on the default skin. - // this will mean that fallback always occurs to the user (then default) skin. - // this may not offer perfect behaviour, but helps keep things simple. - return null; - } - private static SkinInfo createSkinInfo(BeatmapInfo beatmap) => new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata?.AuthorString }; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 337acee9e8..e255fbae81 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -109,9 +109,6 @@ namespace osu.Game.Skinning true) != null); } - [CanBeNull] - protected virtual DefaultLegacySkin CreateFallbackSkin(IResourceStore storage, IStorageResourceProvider resources) => new DefaultLegacySkin(resources); - public override IBindable GetConfig(TLookup lookup) { switch (lookup) From 88b87b98a850b89b4e6f52e47791ba5c60da9f00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 12:10:14 +0900 Subject: [PATCH 0508/1621] Fix slider ball layer sources --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs | 7 +++++-- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index 1a8c5ada1b..e4e1483665 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs @@ -14,18 +14,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { private readonly Drawable animationContent; + private readonly ISkin skin; + private Sprite layerNd; private Sprite layerSpec; - public LegacySliderBall(Drawable animationContent) + public LegacySliderBall(Drawable animationContent, ISkin skin) { this.animationContent = animationContent; + this.skin = skin; AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load() { var ballColour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index e3f32fb76f..3267b48ebf 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -49,13 +49,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return followCircle; case OsuSkinComponents.SliderBall: - var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: ""); + // specular and nd layers must come from the same source as the ball texure. + var ballProvider = Source.FindProvider(s => s.GetTexture("sliderb") != null || s.GetTexture("sliderb0") != null); + + var sliderBallContent = ballProvider.GetAnimation("sliderb", true, true, animationSeparator: ""); // todo: slider ball has a custom frame delay based on velocity // Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME); if (sliderBallContent != null) - return new LegacySliderBall(sliderBallContent); + return new LegacySliderBall(sliderBallContent, ballProvider); return null; From 27e3de3ea33a99abaf453a3eac542221be6c1e61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 12:12:14 +0900 Subject: [PATCH 0509/1621] Add TODO about beatmap skin fallback support --- osu.Game/Skinning/SkinManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index ea4e1232c3..25cd909035 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -235,6 +235,9 @@ namespace osu.Game.Skinning if (selectedSkin != null) return selectedSkin; + // 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) return func(defaultLegacySkin); From 7341e474f1405dde31bd1082cdf7d725d99c61ac Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Jun 2021 14:25:39 +0900 Subject: [PATCH 0510/1621] Attempt to safeguard against collections database corruptions --- osu.Game/Collections/CollectionManager.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 086cc573d5..b53cc659f7 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -264,14 +264,18 @@ namespace osu.Game.Collections using (var sw = new SerializationWriter(storage.GetStream(database_name, FileAccess.Write))) { sw.Write(database_version); - sw.Write(Collections.Count); - foreach (var c in Collections) + var collectionsCopy = Collections.ToArray(); + sw.Write(collectionsCopy.Length); + + foreach (var c in collectionsCopy) { sw.Write(c.Name.Value); - sw.Write(c.Beatmaps.Count); - foreach (var b in c.Beatmaps) + var beatmapsCopy = c.Beatmaps.ToArray(); + sw.Write(beatmapsCopy.Length); + + foreach (var b in beatmapsCopy) sw.Write(b.MD5Hash); } } From f3f634e969607be08ba6e592d4b7454a2e53692f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 15:05:14 +0900 Subject: [PATCH 0511/1621] Clean up previous sample immediately on skin source change to avoid `Play` after disposal This seems to be the simplest way to avoid calls to `Play` after the underlying sample may have been disposed. As per the issue thread, a local workaround is acceptable here. Closes #13223. --- osu.Game/Skinning/PoolableSkinnableSample.cs | 44 +++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index b04158a58f..7565417b7f 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -70,22 +71,48 @@ namespace osu.Game.Skinning updateSample(); } + protected override void LoadComplete() + { + base.LoadComplete(); + + CurrentSkin.SourceChanged += skinChangedImmediate; + } + + private void skinChangedImmediate() + { + // Clean up the previous sample immediately on a source change. + // This avoids a potential call to Play() of an already disposed sample (samples are disposed along with the skin, but SkinChanged is scheduled). + clearPreviousSamples(); + } + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); updateSample(); } + /// + /// Whether this sample was playing before a skin source change. + /// + private bool wasPlaying; + + private void clearPreviousSamples() + { + // only run if the samples aren't already cleared. + // this ensures the "wasPlaying" state is stored correctly even if multiple clear calls are executed. + if (!sampleContainer.Any()) return; + + wasPlaying = Playing; + + sampleContainer.Clear(); + Sample = null; + } + private void updateSample() { if (sampleInfo == null) return; - bool wasPlaying = Playing; - - sampleContainer.Clear(); - Sample = null; - var sample = CurrentSkin.GetSample(sampleInfo); if (sample == null && AllowDefaultFallback) @@ -155,6 +182,13 @@ namespace osu.Game.Skinning } } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + CurrentSkin.SourceChanged -= skinChangedImmediate; + } + #region Re-expose AudioContainer public BindableNumber Volume => sampleContainer.Volume; From e388a896e87e6ad03701bedd286a751b9f965408 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 16:02:25 +0900 Subject: [PATCH 0512/1621] Don't apply visibility increase to first object in osu!catch The goal of the visibility increase is to help in cases where timing is an issue (by showing the approach circle etc.). This doesn't need to apply to catch. @smoogipoo interested as to whether you agree with this one. Visually it looks better to me but it does change the behaviour for only osu!catch, so I'm not 100% confident on it. Closes #13367. --- osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs | 8 -------- osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs | 3 +-- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs index 1248409b2a..09362929d2 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs @@ -4,10 +4,8 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; @@ -21,12 +19,6 @@ namespace osu.Game.Rulesets.Catch.Tests { public class TestSceneCatchModHidden : ModTestScene { - [BackgroundDependencyLoader] - private void load() - { - LocalConfig.SetValue(OsuSetting.IncreaseFirstObjectVisibility, false); - } - [Test] public void TestJuiceStream() { diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index 7bad4c79cb..f9e106f097 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Catch.Mods } protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) - { - } + => ApplyNormalVisibilityState(hitObject, state); protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { From 67135ce3dbaa2ea8a55abba016fd525d16f1ad2a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Jun 2021 16:15:17 +0900 Subject: [PATCH 0513/1621] Add null check --- osu.Game/Skinning/PoolableSkinnableSample.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 7565417b7f..9acc1f1a9d 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -186,7 +186,8 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - CurrentSkin.SourceChanged -= skinChangedImmediate; + if (CurrentSkin != null) + CurrentSkin.SourceChanged -= skinChangedImmediate; } #region Re-expose AudioContainer From 89895f6ce40ab080b0d5cce81763b3a63310ecf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 16:24:00 +0900 Subject: [PATCH 0514/1621] 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 1216b1772f..395470824f 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 21a890014a..9ecab1ee48 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 bf080e4def..e66f125985 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 7fa0ac6ed720b32bcb3c52b05f3cf8352c69927a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Jun 2021 17:03:46 +0900 Subject: [PATCH 0515/1621] Fix possible nullref when exiting song select too fast --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 8 ++++++++ osu.Game/Screens/Select/SongSelect.cs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 0308d74aa4..3694d3afce 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -152,6 +152,14 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for track", () => !Game.MusicController.CurrentTrack.IsDummyDevice && Game.MusicController.IsPlaying); } + [Test] + public void TestPushSongSelectAndPressBackButtonImmediately() + { + AddStep("push song select", () => Game.ScreenStack.Push(new TestPlaySongSelect())); + AddStep("press back button", () => Game.ChildrenOfType().First().Action()); + AddWaitStep("wait two frame", 2); + } + [Test] public void TestExitSongSelectWithClick() { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 270addc8e6..c697c64c3f 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -665,7 +665,7 @@ namespace osu.Game.Screens.Select public override bool OnBackButton() { - if (ModSelect.State.Value == Visibility.Visible) + if (ModSelect?.State.Value == Visibility.Visible) { ModSelect.Hide(); return true; From 490ab9e96a1fed83ed169d9084b095e6af1e9e96 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Jun 2021 17:09:03 +0900 Subject: [PATCH 0516/1621] Fix typo --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 3694d3afce..6fc19b95b9 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Navigation { AddStep("push song select", () => Game.ScreenStack.Push(new TestPlaySongSelect())); AddStep("press back button", () => Game.ChildrenOfType().First().Action()); - AddWaitStep("wait two frame", 2); + AddWaitStep("wait two frames", 2); } [Test] From 860f1aebb385ab9bccdad07848a72a418f500914 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Jun 2021 17:38:12 +0900 Subject: [PATCH 0517/1621] Only call OnBackButton() if the screen has finished loading --- osu.Game/OsuGame.cs | 5 +++-- osu.Game/Screens/IOsuScreen.cs | 3 +++ osu.Game/Screens/Select/SongSelect.cs | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c51624341e..8d87baa363 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -651,9 +651,10 @@ namespace osu.Game Origin = Anchor.BottomLeft, Action = () => { - var currentScreen = ScreenStack.CurrentScreen as IOsuScreen; + if (!(ScreenStack.CurrentScreen is IOsuScreen currentScreen)) + return; - if (currentScreen?.AllowBackButton == true && !currentScreen.OnBackButton()) + if (!((Drawable)currentScreen).IsLoaded || currentScreen.AllowBackButton && !currentScreen.OnBackButton()) ScreenStack.Exit(); } }, diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index cc8778d9ae..0434135547 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -67,8 +67,11 @@ namespace osu.Game.Screens /// Invoked when the back button has been pressed to close any overlays before exiting this . /// /// + /// If this has not yet finished loading, the exit will occur immediately without this method being invoked. + /// /// Return true to block this from being exited after closing an overlay. /// Return false if this should continue exiting. + /// /// bool OnBackButton(); } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index c697c64c3f..270addc8e6 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -665,7 +665,7 @@ namespace osu.Game.Screens.Select public override bool OnBackButton() { - if (ModSelect?.State.Value == Visibility.Visible) + if (ModSelect.State.Value == Visibility.Visible) { ModSelect.Hide(); return true; From ab9290772b46ac2087eeb9f9e5fdfaf848fa03a5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Jun 2021 17:54:54 +0900 Subject: [PATCH 0518/1621] Fix a similar case with online play sub-screens --- .../Navigation/TestSceneScreenNavigation.cs | 28 +++++++++++++++++++ .../Screens/OnlinePlay/OnlinePlayScreen.cs | 5 +++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 6fc19b95b9..52401d32e5 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -9,16 +9,19 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Options; using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Visual.Multiplayer; using osuTK; using osuTK.Input; @@ -306,6 +309,18 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("Toolbar is hidden", () => Game.Toolbar.State.Value == Visibility.Hidden); } + [Test] + public void TestPushMatchSubScreenAndPressBackButtonImmediately() + { + TestMultiplayer multiplayer = null; + + PushAndConfirm(() => multiplayer = new TestMultiplayer()); + + AddStep("open room", () => multiplayer.OpenNewRoom()); + AddStep("press back button", () => Game.ChildrenOfType().First().Action()); + AddWaitStep("wait two frames", 2); + } + private void pushEscape() => AddStep("Press escape", () => InputManager.Key(Key.Escape)); @@ -330,5 +345,18 @@ namespace osu.Game.Tests.Visual.Navigation protected override bool DisplayStableImportPrompt => false; } + + private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer + { + [Cached(typeof(MultiplayerClient))] + public readonly TestMultiplayerClient Client; + + public TestMultiplayer() + { + Client = new TestMultiplayerClient((TestMultiplayerRoomManager)RoomManager); + } + + protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); + } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 90e499c67f..e418d36d40 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -253,7 +253,10 @@ namespace osu.Game.Screens.OnlinePlay public override bool OnBackButton() { - if ((screenStack.CurrentScreen as IOnlinePlaySubScreen)?.OnBackButton() == true) + if (!(screenStack.CurrentScreen is IOnlinePlaySubScreen onlineSubScreen)) + return false; + + if (((Drawable)onlineSubScreen).IsLoaded && onlineSubScreen.AllowBackButton && onlineSubScreen.OnBackButton()) return true; if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen)) From 6e28c1b29a440299d87f81c35dab27a25fc44aa4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 8 Jun 2021 17:54:57 +0900 Subject: [PATCH 0519/1621] Move default catcher sprite to its own file --- .../Skinning/Default/DefaultCatcherSprite.cs | 26 +++++++++++++++++++ osu.Game.Rulesets.Catch/UI/CatcherSprite.cs | 24 ++++------------- 2 files changed, 31 insertions(+), 19 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcherSprite.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcherSprite.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcherSprite.cs new file mode 100644 index 0000000000..f366adf134 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcherSprite.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.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.Catch.UI; + +namespace osu.Game.Rulesets.Catch.Skinning.Default +{ + public class DefaultCatcherSprite : Sprite + { + private readonly CatcherAnimationState state; + + public DefaultCatcherSprite(CatcherAnimationState state) + { + this.state = state; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get($"Gameplay/catch/fruit-catcher-{state.ToString().ToLower()}"); + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs index ef69e3d2d1..a840792597 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs @@ -1,13 +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 osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.Catch.Skinning.Default; using osu.Game.Skinning; using osuTK; +namespace osu.Game.Rulesets.Catch.Skinning.Default +{ +} + namespace osu.Game.Rulesets.Catch.UI { public class CatcherSprite : SkinnableDrawable @@ -39,21 +41,5 @@ namespace osu.Game.Rulesets.Catch.UI return CatchSkinComponents.CatcherIdle; } } - - private class DefaultCatcherSprite : Sprite - { - private readonly CatcherAnimationState state; - - public DefaultCatcherSprite(CatcherAnimationState state) - { - this.state = state; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - Texture = textures.Get($"Gameplay/catch/fruit-catcher-{state.ToString().ToLower()}"); - } - } } } From 4d9fffc01bbcc2863386f0d32f38840cb0077c91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 17:58:57 +0900 Subject: [PATCH 0520/1621] Update score encoder version to be higher than any existing stable version --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index f8dd6953ad..cd77fbbdd8 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -15,7 +15,10 @@ namespace osu.Game.Scoring.Legacy { public class LegacyScoreEncoder { - public const int LATEST_VERSION = 128; + /// + /// Database version in stable-compatible YYYYMMDD format. + /// + public const int LATEST_VERSION = 30000000; private readonly Score score; private readonly IBeatmap beatmap; From 061e3d7f26c22df9da73b81a07cb9c75835a2281 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 18:00:09 +0900 Subject: [PATCH 0521/1621] Move legacy `ScoreInfo` to be completely based on presence of classic mod --- .../Requests/Responses/APILegacyScoreInfo.cs | 8 +++++-- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 4 ++++ osu.Game/Scoring/ScoreInfo.cs | 23 +------------------ 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 3d3c07a5ad..1b394185fd 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -21,7 +21,12 @@ namespace osu.Game.Online.API.Requests.Responses { var ruleset = rulesets.GetRuleset(OnlineRulesetID); - var mods = Mods != null ? ruleset.CreateInstance().GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty(); + var rulesetInstance = ruleset.CreateInstance(); + + var mods = Mods != null ? rulesetInstance.GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty(); + + // all API scores provided by this class are considered to be legacy. + mods = mods.Append(rulesetInstance.GetAllMods().OfType().Single()).ToArray(); var scoreInfo = new ScoreInfo { @@ -38,7 +43,6 @@ namespace osu.Game.Online.API.Requests.Responses Rank = Rank, Ruleset = ruleset, Mods = mods, - IsLegacyScore = true }; if (Statistics != null) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 97cb5ca7ab..4b93530e45 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -65,6 +65,10 @@ namespace osu.Game.Scoring.Legacy scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); + // lazer replays get a really high version number. + if (version < 30000000) + scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.GetAllMods().OfType().Single()).ToArray(); + currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods); scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index a6faaf6379..801175d90d 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -201,33 +201,12 @@ namespace osu.Game.Scoring [JsonProperty("position")] public int? Position { get; set; } - private bool isLegacyScore; - /// /// Whether this represents a legacy (osu!stable) score. /// [JsonIgnore] [NotMapped] - public bool IsLegacyScore - { - get - { - if (isLegacyScore) - return true; - - // The above check will catch legacy online scores that have an appropriate UserString + UserId. - // For non-online scores such as those imported in, a heuristic is used based on the following table: - // - // Mode | UserString | UserId - // --------------- | ---------- | --------- - // stable | | 1 - // lazer | | - // lazer (offline) | Guest | 1 - - return ID > 0 && UserID == 1 && UserString != "Guest"; - } - set => isLegacyScore = value; - } + public bool IsLegacyScore => mods.OfType().Any(); public IEnumerable GetStatisticsForDisplay() { From b287366c8bed5e6f51e98409c6eb3cc6a41150e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 18:09:57 +0900 Subject: [PATCH 0522/1621] Remove forgotten classic mod addition --- osu.Game/Scoring/ScoreInfo.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 801175d90d..755dab17f9 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -76,9 +76,6 @@ namespace osu.Game.Scoring else if (localAPIMods != null) scoreMods = apiMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); - if (IsLegacyScore) - scoreMods = scoreMods.Append(rulesetInstance.GetAllMods().OfType().Single()).ToArray(); - return scoreMods; } set From d31e3e8f1c5d1f41a620f8f9e582a5553d13c1b5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Jun 2021 18:23:03 +0900 Subject: [PATCH 0523/1621] Fix nullref --- osu.Game/Scoring/ScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 755dab17f9..3944c1d3de 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -203,7 +203,7 @@ namespace osu.Game.Scoring ///
[JsonIgnore] [NotMapped] - public bool IsLegacyScore => mods.OfType().Any(); + public bool IsLegacyScore => Mods.OfType().Any(); public IEnumerable GetStatisticsForDisplay() { From 4ee7721c51ae5300f80a71f0f58de993530c2211 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Jun 2021 18:38:47 +0900 Subject: [PATCH 0524/1621] Extract first version out to constant --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 4b93530e45..2f17167297 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -66,7 +66,7 @@ namespace osu.Game.Scoring.Legacy scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); // lazer replays get a really high version number. - if (version < 30000000) + if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION) scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.GetAllMods().OfType().Single()).ToArray(); currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index cd77fbbdd8..288552879c 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -17,8 +17,14 @@ namespace osu.Game.Scoring.Legacy { /// /// Database version in stable-compatible YYYYMMDD format. + /// Should be incremented if any changes are made to the format/usage. /// - public const int LATEST_VERSION = 30000000; + public const int LATEST_VERSION = FIRST_LAZER_VERSION; + + /// + /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. + /// + public const int FIRST_LAZER_VERSION = 30000000; private readonly Score score; private readonly IBeatmap beatmap; From 0b9916b266a9be0eb888cb0484ea2d05216ed71d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Jun 2021 18:39:52 +0900 Subject: [PATCH 0525/1621] Add parens to declare operator precedence --- 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 8d87baa363..2dca91cbf3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -654,7 +654,7 @@ namespace osu.Game if (!(ScreenStack.CurrentScreen is IOsuScreen currentScreen)) return; - if (!((Drawable)currentScreen).IsLoaded || currentScreen.AllowBackButton && !currentScreen.OnBackButton()) + if (!((Drawable)currentScreen).IsLoaded || (currentScreen.AllowBackButton && !currentScreen.OnBackButton())) ScreenStack.Exit(); } }, From f1bef989b748476ab0914f573f1e05754b579778 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Tue, 8 Jun 2021 19:43:59 +1000 Subject: [PATCH 0526/1621] 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 0527/1621] 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 0192549d6cea54375398d89d564dc75a36161863 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 8 Jun 2021 19:51:40 +0900 Subject: [PATCH 0528/1621] Refactor catcher sprite to use skinned piece pattern --- .../CatchSkinComponents.cs | 4 +- .../Skinning/Default/DefaultCatcher.cs | 54 +++++++++++++ .../Skinning/Default/DefaultCatcherSprite.cs | 26 ------- .../Skinning/ICatcherPiece.cs | 12 +++ .../Legacy/CatchLegacySkinTransformer.cs | 15 ++-- .../Skinning/Legacy/LegacyCatcher.cs | 76 +++++++++++++++++++ osu.Game.Rulesets.Catch/UI/Catcher.cs | 63 ++++----------- osu.Game.Rulesets.Catch/UI/CatcherSprite.cs | 45 ----------- .../UI/CatcherTrailDisplay.cs | 6 +- 9 files changed, 163 insertions(+), 138 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs delete mode 100644 osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcherSprite.cs create mode 100644 osu.Game.Rulesets.Catch/Skinning/ICatcherPiece.cs create mode 100644 osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcher.cs delete mode 100644 osu.Game.Rulesets.Catch/UI/CatcherSprite.cs diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs index 668f7197be..e736d68740 100644 --- a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs +++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs @@ -8,9 +8,7 @@ namespace osu.Game.Rulesets.Catch Fruit, Banana, Droplet, - CatcherIdle, - CatcherFail, - CatcherKiai, + Catcher, CatchComboCounter } } diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs new file mode 100644 index 0000000000..655e557705 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs @@ -0,0 +1,54 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.Catch.UI; + +namespace osu.Game.Rulesets.Catch.Skinning.Default +{ + public class DefaultCatcher : CompositeDrawable, ICatcherPiece + { + public Bindable CurrentState { get; } = new Bindable(); + + public Texture CurrentTexture => sprite.Texture; + + private readonly Sprite sprite; + + private readonly Dictionary textures = new Dictionary(); + + public DefaultCatcher() + { + RelativeSizeAxes = Axes.Both; + InternalChild = sprite = new Sprite + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore store, Bindable currentState) + { + CurrentState.BindTo(currentState); + + textures[CatcherAnimationState.Idle] = store.Get(@"Gameplay/catch/fruit-catcher-idle"); + textures[CatcherAnimationState.Fail] = store.Get(@"Gameplay/catch/fruit-catcher-fail"); + textures[CatcherAnimationState.Kiai] = store.Get(@"Gameplay/catch/fruit-catcher-kiai"); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + CurrentState.BindValueChanged(state => sprite.Texture = textures[state.NewValue], true); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcherSprite.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcherSprite.cs deleted file mode 100644 index f366adf134..0000000000 --- a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcherSprite.cs +++ /dev/null @@ -1,26 +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.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Rulesets.Catch.UI; - -namespace osu.Game.Rulesets.Catch.Skinning.Default -{ - public class DefaultCatcherSprite : Sprite - { - private readonly CatcherAnimationState state; - - public DefaultCatcherSprite(CatcherAnimationState state) - { - this.state = state; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - Texture = textures.Get($"Gameplay/catch/fruit-catcher-{state.ToString().ToLower()}"); - } - } -} diff --git a/osu.Game.Rulesets.Catch/Skinning/ICatcherPiece.cs b/osu.Game.Rulesets.Catch/Skinning/ICatcherPiece.cs new file mode 100644 index 0000000000..0b51846eda --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/ICatcherPiece.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.Textures; + +namespace osu.Game.Rulesets.Catch.Skinning +{ + public interface ICatcherPiece + { + Texture CurrentTexture { get; } + } +} diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 8c9e602cd4..2713f17789 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -65,17 +65,12 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy return null; - case CatchSkinComponents.CatcherIdle: - return this.GetAnimation("fruit-catcher-idle", true, true, true) ?? - this.GetAnimation("fruit-ryuuta", true, true, true); + case CatchSkinComponents.Catcher: + if (this.GetAnimation(@"fruit-ryuuta", true, true) != null || + this.GetAnimation(@"fruit-catcher-idle", true, true) != null) + return new LegacyCatcher(); - case CatchSkinComponents.CatcherFail: - return this.GetAnimation("fruit-catcher-fail", true, true, true) ?? - this.GetAnimation("fruit-ryuuta", true, true, true); - - case CatchSkinComponents.CatcherKiai: - return this.GetAnimation("fruit-catcher-kiai", true, true, true) ?? - this.GetAnimation("fruit-ryuuta", true, true, true); + return null; case CatchSkinComponents.CatchComboCounter: if (providesComboCounter) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcher.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcher.cs new file mode 100644 index 0000000000..4bf5dd9dd8 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcher.cs @@ -0,0 +1,76 @@ +// 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.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 LegacyCatcher : CompositeDrawable, ICatcherPiece + { + public Bindable CurrentState { get; } = new Bindable(); + + public Texture CurrentTexture => (currentDrawable as TextureAnimation)?.CurrentFrame ?? (currentDrawable as Sprite)?.Texture; + + private readonly Dictionary drawables = new Dictionary(); + + private Drawable currentDrawable; + + public LegacyCatcher() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, Bindable currentState) + { + CurrentState.BindTo(currentState); + + AddRangeInternal(new[] + { + drawables[CatcherAnimationState.Idle] = getDrawableFor(@"fruit-catcher-idle"), + drawables[CatcherAnimationState.Fail] = getDrawableFor(@"fruit-catcher-fail"), + drawables[CatcherAnimationState.Kiai] = getDrawableFor(@"fruit-catcher-kiai"), + }); + currentDrawable = drawables[CatcherAnimationState.Idle]; + + foreach (var d in drawables.Values) + { + d.Anchor = Anchor.TopCentre; + d.Origin = Anchor.TopCentre; + d.RelativeSizeAxes = Axes.Both; + d.Size = Vector2.One; + d.FillMode = FillMode.Fit; + d.Alpha = 0; + } + + Drawable getDrawableFor(string name) => + skin.GetAnimation(name, true, true, true) ?? + skin.GetAnimation(@"fruit-ryuuta", true, true, true) ?? + skin.GetAnimation(@"fruit-catcher-idle", true, true, true); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + CurrentState.BindValueChanged(state => + { + currentDrawable.Alpha = 0; + currentDrawable = drawables[state.NewValue]; + currentDrawable.Alpha = 1; + + (currentDrawable as IFramedAnimation)?.GotoFrame(0); + }, true); + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 1c29e8b20c..9daf6e23ab 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -7,9 +7,9 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Animations; 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; @@ -18,6 +18,7 @@ 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; @@ -78,17 +79,17 @@ namespace osu.Game.Rulesets.Catch.UI ///
private readonly Container droppedObjectTarget; - public CatcherAnimationState CurrentState { get; private set; } + [Cached] + public readonly Bindable CurrentStateBindable = new Bindable(); + + public CatcherAnimationState CurrentState => CurrentStateBindable.Value; /// /// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable. /// public const float ALLOWED_CATCH_RANGE = 0.8f; - /// - /// The drawable catcher for . - /// - internal Drawable CurrentDrawableCatcher => currentCatcher.Drawable; + internal Texture CurrentTexture => currentCatcherPiece.CurrentTexture; private bool dashing; @@ -110,11 +111,9 @@ namespace osu.Game.Rulesets.Catch.UI ///
private readonly float catchWidth; - private readonly CatcherSprite catcherIdle; - private readonly CatcherSprite catcherKiai; - private readonly CatcherSprite catcherFail; + private readonly SkinnableDrawable currentCatcher; - private CatcherSprite currentCatcher; + private ICatcherPiece currentCatcherPiece => (ICatcherPiece)currentCatcher.Drawable; private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR; private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR; @@ -156,20 +155,12 @@ namespace osu.Game.Rulesets.Catch.UI Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, }, - catcherIdle = new CatcherSprite(CatcherAnimationState.Idle) + currentCatcher = new SkinnableDrawable( + new CatchSkinComponent(CatchSkinComponents.Catcher), + _ => new DefaultCatcher()) { Anchor = Anchor.TopCentre, - Alpha = 0, - }, - catcherKiai = new CatcherSprite(CatcherAnimationState.Kiai) - { - Anchor = Anchor.TopCentre, - Alpha = 0, - }, - catcherFail = new CatcherSprite(CatcherAnimationState.Fail) - { - Anchor = Anchor.TopCentre, - Alpha = 0, + OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE }, hitExplosionContainer = new HitExplosionContainer { @@ -184,8 +175,6 @@ namespace osu.Game.Rulesets.Catch.UI { hitLighting = config.GetBindable(OsuSetting.HitLighting); trails = new CatcherTrailDisplay(this); - - updateCatcher(); } protected override void LoadComplete() @@ -436,36 +425,12 @@ namespace osu.Game.Rulesets.Catch.UI } } - private void updateCatcher() - { - currentCatcher?.Hide(); - - switch (CurrentState) - { - default: - currentCatcher = catcherIdle; - break; - - case CatcherAnimationState.Fail: - currentCatcher = catcherFail; - break; - - case CatcherAnimationState.Kiai: - currentCatcher = catcherKiai; - break; - } - - currentCatcher.Show(); - (currentCatcher.Drawable as IFramedAnimation)?.GotoFrame(0); - } - private void updateState(CatcherAnimationState state) { if (CurrentState == state) return; - CurrentState = state; - updateCatcher(); + CurrentStateBindable.Value = state; } private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Vector2 position) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs deleted file mode 100644 index a840792597..0000000000 --- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs +++ /dev/null @@ -1,45 +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.Game.Rulesets.Catch.Skinning.Default; -using osu.Game.Skinning; -using osuTK; - -namespace osu.Game.Rulesets.Catch.Skinning.Default -{ -} - -namespace osu.Game.Rulesets.Catch.UI -{ - public class CatcherSprite : SkinnableDrawable - { - protected override bool ApplySizeRestrictionsToDefault => true; - - public CatcherSprite(CatcherAnimationState state) - : base(new CatchSkinComponent(componentFromState(state)), _ => - new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleToFit) - { - RelativeSizeAxes = Axes.None; - 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; - } - - private static CatchSkinComponents componentFromState(CatcherAnimationState state) - { - switch (state) - { - case CatcherAnimationState.Fail: - return CatchSkinComponents.CatcherFail; - - case CatcherAnimationState.Kiai: - return CatchSkinComponents.CatcherKiai; - - default: - return CatchSkinComponents.CatcherIdle; - } - } - } -} diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index fa65190032..0aef215797 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -4,10 +4,8 @@ using System; using JetBrains.Annotations; using osu.Framework.Graphics; -using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; -using osu.Framework.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -120,11 +118,9 @@ namespace osu.Game.Rulesets.Catch.UI private CatcherTrailSprite createTrailSprite(Container target) { - var texture = (catcher.CurrentDrawableCatcher as TextureAnimation)?.CurrentFrame ?? ((Sprite)catcher.CurrentDrawableCatcher).Texture; - CatcherTrailSprite sprite = trailPool.Get(); - sprite.Texture = texture; + sprite.Texture = catcher.CurrentTexture; sprite.Anchor = catcher.Anchor; sprite.Scale = catcher.Scale; sprite.Blending = BlendingParameters.Additive; From 109a366722bb6f46a5d3304da7a6742faa988fb8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 8 Jun 2021 21:58:39 +0900 Subject: [PATCH 0529/1621] Use separate classes for old and new catcher legacy skin element - Fix catcher texture animation is reset for legacy old catcher skin --- .../Legacy/CatchLegacySkinTransformer.cs | 11 ++++-- .../{LegacyCatcher.cs => LegacyCatcherNew.cs} | 25 ++++++------- .../Skinning/Legacy/LegacyCatcherOld.cs | 37 +++++++++++++++++++ 3 files changed, 56 insertions(+), 17 deletions(-) rename osu.Game.Rulesets.Catch/Skinning/Legacy/{LegacyCatcher.cs => LegacyCatcherNew.cs} (75%) create mode 100644 osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 2713f17789..6a9a3c43a2 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -66,9 +66,14 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy return null; case CatchSkinComponents.Catcher: - if (this.GetAnimation(@"fruit-ryuuta", true, true) != null || - this.GetAnimation(@"fruit-catcher-idle", true, true) != null) - return new LegacyCatcher(); + // New elements will be ignored when the old element exists. + if (GetTexture(@"fruit-ryuuta") != null || + GetTexture(@"fruit-ryuuta-0") != null) + return new LegacyCatcherOld(); + + if (GetTexture(@"fruit-catcher-idle") != null || + GetTexture(@"fruit-catcher-idle-0") != null) + return new LegacyCatcherNew(); return null; diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcher.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs similarity index 75% rename from osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcher.cs rename to osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs index 4bf5dd9dd8..4e662869c9 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcher.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,7 +17,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Skinning.Legacy { - public class LegacyCatcher : CompositeDrawable, ICatcherPiece + public class LegacyCatcherNew : CompositeDrawable, ICatcherPiece { public Bindable CurrentState { get; } = new Bindable(); @@ -25,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy private Drawable currentDrawable; - public LegacyCatcher() + public LegacyCatcherNew() { RelativeSizeAxes = Axes.Both; } @@ -35,27 +37,22 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy { CurrentState.BindTo(currentState); - AddRangeInternal(new[] - { - drawables[CatcherAnimationState.Idle] = getDrawableFor(@"fruit-catcher-idle"), - drawables[CatcherAnimationState.Fail] = getDrawableFor(@"fruit-catcher-fail"), - drawables[CatcherAnimationState.Kiai] = getDrawableFor(@"fruit-catcher-kiai"), - }); - currentDrawable = drawables[CatcherAnimationState.Idle]; - - foreach (var d in drawables.Values) + foreach (var state in Enum.GetValues(typeof(CatcherAnimationState)).Cast()) { + var d = getDrawableFor(state); d.Anchor = Anchor.TopCentre; d.Origin = Anchor.TopCentre; d.RelativeSizeAxes = Axes.Both; d.Size = Vector2.One; d.FillMode = FillMode.Fit; d.Alpha = 0; + AddInternal(drawables[state] = d); } - Drawable getDrawableFor(string name) => - skin.GetAnimation(name, true, true, true) ?? - skin.GetAnimation(@"fruit-ryuuta", true, true, true) ?? + currentDrawable = drawables[CatcherAnimationState.Idle]; + + Drawable getDrawableFor(CatcherAnimationState state) => + skin.GetAnimation(@$"fruit-catcher-{state.ToString().ToLowerInvariant()}", true, true, true) ?? skin.GetAnimation(@"fruit-catcher-idle", true, true, true); } diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs new file mode 100644 index 0000000000..c426856c27 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.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, ICatcherPiece + { + public Texture CurrentTexture => (InternalChild as TextureAnimation)?.CurrentFrame ?? (InternalChild as Sprite)?.Texture; + + public LegacyCatcherOld() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + InternalChild = skin.GetAnimation(@"fruit-ryuuta", true, true, true).With(d => + { + d.Anchor = Anchor.TopCentre; + d.Origin = Anchor.TopCentre; + d.RelativeSizeAxes = Axes.Both; + d.Size = Vector2.One; + d.FillMode = FillMode.Fit; + }); + } + } +} From 194c78f67aab618f6fb48b1c4983279c192105b5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 8 Jun 2021 22:08:54 +0900 Subject: [PATCH 0530/1621] Make current state bindable protected --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 9daf6e23ab..a9be2cde1c 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly Container droppedObjectTarget; [Cached] - public readonly Bindable CurrentStateBindable = new Bindable(); + protected readonly Bindable CurrentStateBindable = new Bindable(); public CatcherAnimationState CurrentState => CurrentStateBindable.Value; From 7df971a9709f98e061434d86c4fecb02a992a508 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 8 Jun 2021 22:10:13 +0900 Subject: [PATCH 0531/1621] `ICatcherPiece` -> `ICatcherSprite` --- osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs | 2 +- .../Skinning/{ICatcherPiece.cs => ICatcherSprite.cs} | 2 +- osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs | 2 +- osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs | 2 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 4 +--- 5 files changed, 5 insertions(+), 7 deletions(-) rename osu.Game.Rulesets.Catch/Skinning/{ICatcherPiece.cs => ICatcherSprite.cs} (88%) diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs index 655e557705..364fc211a0 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Catch.UI; namespace osu.Game.Rulesets.Catch.Skinning.Default { - public class DefaultCatcher : CompositeDrawable, ICatcherPiece + public class DefaultCatcher : CompositeDrawable, ICatcherSprite { public Bindable CurrentState { get; } = new Bindable(); diff --git a/osu.Game.Rulesets.Catch/Skinning/ICatcherPiece.cs b/osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs similarity index 88% rename from osu.Game.Rulesets.Catch/Skinning/ICatcherPiece.cs rename to osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs index 0b51846eda..073868e947 100644 --- a/osu.Game.Rulesets.Catch/Skinning/ICatcherPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics.Textures; namespace osu.Game.Rulesets.Catch.Skinning { - public interface ICatcherPiece + 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 4e662869c9..5987e9e393 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs @@ -17,7 +17,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Skinning.Legacy { - public class LegacyCatcherNew : CompositeDrawable, ICatcherPiece + public class LegacyCatcherNew : CompositeDrawable, ICatcherSprite { public Bindable CurrentState { get; } = new Bindable(); diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs index c426856c27..a8948d2ed0 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs @@ -12,7 +12,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Skinning.Legacy { - public class LegacyCatcherOld : CompositeDrawable, ICatcherPiece + public class LegacyCatcherOld : CompositeDrawable, ICatcherSprite { public Texture CurrentTexture => (InternalChild as TextureAnimation)?.CurrentFrame ?? (InternalChild as Sprite)?.Texture; diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index a9be2cde1c..799743ae6e 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Catch.UI ///
public const float ALLOWED_CATCH_RANGE = 0.8f; - internal Texture CurrentTexture => currentCatcherPiece.CurrentTexture; + internal Texture CurrentTexture => ((ICatcherSprite)currentCatcher.Drawable).CurrentTexture; private bool dashing; @@ -113,8 +113,6 @@ namespace osu.Game.Rulesets.Catch.UI private readonly SkinnableDrawable currentCatcher; - private ICatcherPiece currentCatcherPiece => (ICatcherPiece)currentCatcher.Drawable; - private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR; private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR; From 6b127f50f21afb89a5bbc2ff8f5364a4bd1f9763 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Tue, 8 Jun 2021 09:14:04 -0400 Subject: [PATCH 0532/1621] 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 0533/1621] 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 0534/1621] 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 0535/1621] 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 c3ea1b26e1b519bb876b13841e9557ced5ed2e5e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Jun 2021 22:48:07 +0900 Subject: [PATCH 0536/1621] Fix DT being doubled in multiplayer spectator --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 277aa5d772..983daac909 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -22,6 +22,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate // Isolates beatmap/ruleset to this screen. public override bool DisallowExternalBeatmapRulesetChanges => true; + // We are managing our own adjustments. For now, this happens inside the Player instances themselves. + public override bool AllowRateAdjustments => false; + /// /// Whether all spectating players have finished loading. /// From c8e14d771033917af1b42339af7bdba0647e3fb1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Jun 2021 23:09:18 +0900 Subject: [PATCH 0537/1621] Ignore non-scorable and bonus judgements --- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 28 +++++++++++++++++++ .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 3 ++ .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 9 +++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 2a12577ad8..7accaef818 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -109,6 +109,34 @@ namespace osu.Game.Tests.Visual.Gameplay meter => meter.ChildrenOfType().Count() == 2)); } + [Test] + public void TestBonus() + { + AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1)); + + AddStep("small bonus", () => newJudgement(result: HitResult.SmallBonus)); + AddAssert("no bars added", () => !this.ChildrenOfType().Any()); + AddAssert("no circle added", () => !this.ChildrenOfType().Any()); + + AddStep("large bonus", () => newJudgement(result: HitResult.LargeBonus)); + AddAssert("no bars added", () => !this.ChildrenOfType().Any()); + AddAssert("no circle added", () => !this.ChildrenOfType().Any()); + } + + [Test] + public void TestIgnore() + { + AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1)); + + AddStep("ignore hit", () => newJudgement(result: HitResult.IgnoreHit)); + AddAssert("no bars added", () => !this.ChildrenOfType().Any()); + AddAssert("no circle added", () => !this.ChildrenOfType().Any()); + + AddStep("ignore miss", () => newJudgement(result: HitResult.IgnoreMiss)); + AddAssert("no bars added", () => !this.ChildrenOfType().Any()); + AddAssert("no circle added", () => !this.ChildrenOfType().Any()); + } + private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) { hitWindows?.SetDifficulty(overallDifficulty); diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 0412085d1d..89f61785e8 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -217,6 +217,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters if (!judgement.IsHit || judgement.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0) return; + if (!judgement.Type.IsScorable() || judgement.Type.IsBonus()) + return; + if (judgementsContainer.Count > max_concurrent_judgements) { const double quick_fade_time = 100; diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 86c0de8855..dda2a6da95 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -24,7 +25,13 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters InternalChild = judgementsFlow = new JudgementFlow(); } - protected override void OnNewJudgement(JudgementResult judgement) => judgementsFlow.Push(GetColourForHitResult(judgement.Type)); + protected override void OnNewJudgement(JudgementResult judgement) + { + if (!judgement.Type.IsScorable() || judgement.Type.IsBonus()) + return; + + judgementsFlow.Push(GetColourForHitResult(judgement.Type)); + } private class JudgementFlow : FillFlowContainer { From 2c88e6df8de34ac1bc5e637bc4d1564d48699dd1 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Tue, 8 Jun 2021 10:08:02 -0400 Subject: [PATCH 0538/1621] 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 00efed2c39d67d0d5f4c5b0d21509ebea2fef205 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Jun 2021 23:10:21 +0900 Subject: [PATCH 0539/1621] Add colours for tick judgements --- osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index 17a6e772fb..9844b9f10d 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -41,6 +41,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { switch (result) { + case HitResult.SmallTickMiss: + case HitResult.LargeTickMiss: case HitResult.Miss: return colours.Red; @@ -53,6 +55,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters case HitResult.Good: return colours.GreenLight; + case HitResult.SmallTickHit: + case HitResult.LargeTickHit: case HitResult.Great: return colours.Blue; From d212918d67059ecd111155f93c039cf50435a2a8 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Tue, 8 Jun 2021 10:14:07 -0400 Subject: [PATCH 0540/1621] 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 0541/1621] 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 88266eac63b54be330837c4b2c77bca522012aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Jun 2021 16:33:56 +0200 Subject: [PATCH 0542/1621] Add option to fix label width of a `LabelledDrawable` --- .../TestSceneLabelledDrawable.cs | 42 +++++++++++++++ .../UserInterfaceV2/LabelledDrawable.cs | 51 +++++++++++++++++-- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs index 8179f92ffc..fe312ccc8f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs @@ -2,11 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; +using osuTK; using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface @@ -21,6 +24,45 @@ namespace osu.Game.Tests.Visual.UserInterface [TestCase(true)] public void TestNonPadded(bool hasDescription) => createPaddedComponent(hasDescription, false); + [Test] + public void TestFixedWidth() + { + const float label_width = 200; + + AddStep("create components", () => Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new NonPaddedLabelledDrawable + { + Label = "short", + FixedLabelWidth = label_width + }, + new NonPaddedLabelledDrawable + { + Label = "very very very very very very very very very very very long", + FixedLabelWidth = label_width + }, + new PaddedLabelledDrawable + { + Label = "short", + FixedLabelWidth = label_width + }, + new PaddedLabelledDrawable + { + Label = "very very very very very very very very very very very long", + FixedLabelWidth = label_width + } + } + }); + + AddStep("unset label width", () => this.ChildrenOfType>().ForEach(d => d.FixedLabelWidth = null)); + AddStep("reset label width", () => this.ChildrenOfType>().ForEach(d => d.FixedLabelWidth = label_width)); + } + private void createPaddedComponent(bool hasDescription = false, bool padded = true) { AddStep("create component", () => diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs index ec68223a3d..5a697623c9 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -14,6 +14,27 @@ namespace osu.Game.Graphics.UserInterfaceV2 public abstract class LabelledDrawable : CompositeDrawable where T : Drawable { + private float? fixedLabelWidth; + + /// + /// The fixed width of the label of this . + /// If null, the label portion will auto-size to its content. + /// Can be used in layout scenarios where several labels must match in length for the components to be aligned properly. + /// + public float? FixedLabelWidth + { + get => fixedLabelWidth; + set + { + if (fixedLabelWidth == value) + return; + + fixedLabelWidth = value; + + updateLabelWidth(); + } + } + protected const float CONTENT_PADDING_VERTICAL = 10; protected const float CONTENT_PADDING_HORIZONTAL = 15; protected const float CORNER_RADIUS = 15; @@ -23,6 +44,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 ///
protected readonly T Component; + private readonly GridContainer grid; private readonly OsuTextFlowContainer labelText; private readonly OsuTextFlowContainer descriptionText; @@ -56,7 +78,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Spacing = new Vector2(0, 12), Children = new Drawable[] { - new GridContainer + grid = new GridContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -69,7 +91,13 @@ namespace osu.Game.Graphics.UserInterfaceV2 Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 20 } + Padding = new MarginPadding + { + Right = 20, + // ensure that the label is always vertically padded even if the component itself isn't. + // this may become an issue if the label is taller than the component. + Vertical = padded ? 0 : CONTENT_PADDING_VERTICAL + } }, new Container { @@ -87,7 +115,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 }, }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, - ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }, descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) { @@ -99,6 +126,24 @@ namespace osu.Game.Graphics.UserInterfaceV2 } } }; + + updateLabelWidth(); + } + + private void updateLabelWidth() + { + if (fixedLabelWidth == null) + { + grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }; + labelText.RelativeSizeAxes = Axes.None; + labelText.AutoSizeAxes = Axes.Both; + } + else + { + grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.Absolute, fixedLabelWidth.Value) }; + labelText.AutoSizeAxes = Axes.Y; + labelText.RelativeSizeAxes = Axes.X; + } } [BackgroundDependencyLoader] From 410cb16340fc7212070a456ff7412cfb9a2204af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Jun 2021 17:14:26 +0200 Subject: [PATCH 0543/1621] Apply fixed label width to setup screen items --- osu.Game/Screens/Edit/Setup/ColoursSection.cs | 1 + osu.Game/Screens/Edit/Setup/DifficultySection.cs | 4 ++++ osu.Game/Screens/Edit/Setup/MetadataSection.cs | 4 ++++ osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 2 ++ osu.Game/Screens/Edit/Setup/SetupSection.cs | 6 ++++++ 5 files changed, 17 insertions(+) diff --git a/osu.Game/Screens/Edit/Setup/ColoursSection.cs b/osu.Game/Screens/Edit/Setup/ColoursSection.cs index cb7deadcb7..4a81959a54 100644 --- a/osu.Game/Screens/Edit/Setup/ColoursSection.cs +++ b/osu.Game/Screens/Edit/Setup/ColoursSection.cs @@ -25,6 +25,7 @@ namespace osu.Game.Screens.Edit.Setup comboColours = new LabelledColourPalette { Label = "Hitcircle / Slider Combos", + FixedLabelWidth = LABEL_WIDTH, ColourNamePrefix = "Combo" } }; diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index 493d3ed20c..a8800d524f 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -28,6 +28,7 @@ namespace osu.Game.Screens.Edit.Setup circleSizeSlider = new LabelledSliderBar { Label = "Object Size", + FixedLabelWidth = LABEL_WIDTH, Description = "The size of all hit objects", Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.CircleSize) { @@ -40,6 +41,7 @@ namespace osu.Game.Screens.Edit.Setup healthDrainSlider = new LabelledSliderBar { Label = "Health Drain", + FixedLabelWidth = LABEL_WIDTH, Description = "The rate of passive health drain throughout playable time", Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.DrainRate) { @@ -52,6 +54,7 @@ namespace osu.Game.Screens.Edit.Setup approachRateSlider = new LabelledSliderBar { Label = "Approach Rate", + FixedLabelWidth = LABEL_WIDTH, Description = "The speed at which objects are presented to the player", Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate) { @@ -64,6 +67,7 @@ namespace osu.Game.Screens.Edit.Setup overallDifficultySlider = new LabelledSliderBar { Label = "Overall Difficulty", + FixedLabelWidth = LABEL_WIDTH, Description = "The harshness of hit windows and difficulty of special objects (ie. spinners)", Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) { diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 889a5eab5e..c79888b9d1 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -27,24 +27,28 @@ namespace osu.Game.Screens.Edit.Setup 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 }, diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 12270f2aa4..ba22c82ecc 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -54,6 +54,7 @@ namespace osu.Game.Screens.Edit.Setup backgroundTextBox = new FileChooserLabelledTextBox(".jpg", ".jpeg", ".png") { Label = "Background", + FixedLabelWidth = LABEL_WIDTH, PlaceholderText = "Click to select a background image", Current = { Value = working.Value.Metadata.BackgroundFile }, Target = backgroundFileChooserContainer, @@ -72,6 +73,7 @@ namespace osu.Game.Screens.Edit.Setup audioTrackTextBox = new FileChooserLabelledTextBox(".mp3", ".ogg") { Label = "Audio Track", + FixedLabelWidth = LABEL_WIDTH, PlaceholderText = "Click to select a track", Current = { Value = working.Value.Metadata.AudioFile }, Target = audioTrackFileChooserContainer, diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs index 8964e651df..560e6fff67 100644 --- a/osu.Game/Screens/Edit/Setup/SetupSection.cs +++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; using osuTK; namespace osu.Game.Screens.Edit.Setup @@ -15,6 +16,11 @@ namespace osu.Game.Screens.Edit.Setup { private readonly FillFlowContainer flow; + /// + /// Used to align some of the child s together to achieve a grid-like look. + /// + protected const float LABEL_WIDTH = 160; + [Resolved] protected OsuColour Colours { get; private set; } From 2484ccd50c325ae9545f84e4ef3ecb9035a1d5d4 Mon Sep 17 00:00:00 2001 From: Robin Avery Date: Tue, 8 Jun 2021 11:49:25 -0400 Subject: [PATCH 0544/1621] Ensure scale x or y does not go below zero in SkinSelectionHandler.HandleScale --- 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 9cca0ba2c7..2d790eed5b 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -95,8 +95,8 @@ namespace osu.Game.Skinning.Editor // scale adjust applied to each individual item should match that of the quad itself. var scaledDelta = new Vector2( - adjustedRect.Width / selectionRect.Width, - adjustedRect.Height / selectionRect.Height + MathF.Max(adjustedRect.Width / selectionRect.Width, 0), + MathF.Max(adjustedRect.Height / selectionRect.Height, 0) ); foreach (var b in SelectedBlueprints) From 5bf4dd63588324b8b8cc409d2940cfe43223c698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Jun 2021 21:57:08 +0200 Subject: [PATCH 0545/1621] Move skin background to separate file --- .../Graphics/Backgrounds/SkinBackground.cs | 25 +++++++++++++++++++ .../Backgrounds/BackgroundScreenDefault.cs | 17 ------------- 2 files changed, 25 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Graphics/Backgrounds/SkinBackground.cs diff --git a/osu.Game/Graphics/Backgrounds/SkinBackground.cs b/osu.Game/Graphics/Backgrounds/SkinBackground.cs new file mode 100644 index 0000000000..8be017dc91 --- /dev/null +++ b/osu.Game/Graphics/Backgrounds/SkinBackground.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 osu.Framework.Allocation; +using osu.Game.Skinning; + +namespace osu.Game.Graphics.Backgrounds +{ + internal class SkinBackground : Background + { + private readonly Skin skin; + + public SkinBackground(Skin skin, string fallbackTextureName) + : base(fallbackTextureName) + { + this.skin = skin; + } + + [BackgroundDependencyLoader] + private void load() + { + Sprite.Texture = skin.GetTexture("menu-background") ?? Sprite.Texture; + } + } +} diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 6bcfaac907..abfbb64f61 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -140,22 +140,5 @@ namespace osu.Game.Screens.Backgrounds return $@"Menu/menu-background-{currentDisplay % background_count + 1}"; } } - - private class SkinnedBackground : Background - { - private readonly Skin skin; - - public SkinnedBackground(Skin skin, string fallbackTextureName) - : base(fallbackTextureName) - { - this.skin = skin; - } - - [BackgroundDependencyLoader] - private void load() - { - Sprite.Texture = skin.GetTexture("menu-background") ?? Sprite.Texture; - } - } } } From d86ace4d11b10ce6af7ed61527007ea8c53c55dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Jun 2021 21:58:44 +0200 Subject: [PATCH 0546/1621] Add test coverage for skin background source --- .../Visual/Background/TestSceneBackgroundScreenDefault.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index 09fe9b3767..c574d89ad9 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Background } [Test] - public void TestTogglingStoryboardSwitchesBackgroundType() + public void TestBackgroundTypeSwitch() { setSupporter(true); @@ -44,6 +44,9 @@ namespace osu.Game.Tests.Visual.Background setSourceMode(BackgroundSource.BeatmapWithStoryboard); AddUntilStep("is storyboard background", () => getCurrentBackground() is BeatmapBackgroundWithStoryboard); + + setSourceMode(BackgroundSource.Skin); + AddUntilStep("is skin background", () => getCurrentBackground() is SkinBackground); } [Test] @@ -78,7 +81,7 @@ namespace osu.Game.Tests.Visual.Background } private void setSourceMode(BackgroundSource source) => - AddStep("set background mode to beatmap", () => config.SetValue(OsuSetting.MenuBackgroundSource, source)); + AddStep($"set background mode to {source}", () => config.SetValue(OsuSetting.MenuBackgroundSource, source)); private void setSupporter(bool isSupporter) => AddStep($"set supporter {isSupporter}", () => ((DummyAPIAccess)API).LocalUser.Value = new User From a98c302211d61d3139f0e981ebeb52e0c6680f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Jun 2021 22:04:59 +0200 Subject: [PATCH 0547/1621] Bring back skin background source --- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index abfbb64f61..46574ff8c8 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -120,6 +120,10 @@ namespace osu.Game.Screens.Backgrounds break; } + + case BackgroundSource.Skin: + newBackground = new SkinBackground(skin.Value, getBackgroundTextureName()); + break; } } From f628ec25ef9b80b1b88f5d380f4322328bbdafd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Jun 2021 22:25:29 +0200 Subject: [PATCH 0548/1621] Add test coverage for keeping same background instance --- .../Background/TestSceneBackgroundScreenDefault.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index c574d89ad9..135998695b 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.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.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -64,15 +65,17 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("is beatmap background", () => getCurrentBackground() is BeatmapBackground); } - [Test] - public void TestBeatmapDoesntReloadOnNoChange() + [TestCase(BackgroundSource.Beatmap, typeof(BeatmapBackground))] + [TestCase(BackgroundSource.BeatmapWithStoryboard, typeof(BeatmapBackgroundWithStoryboard))] + [TestCase(BackgroundSource.Skin, typeof(SkinBackground))] + public void TestBackgroundDoesntReloadOnNoChange(BackgroundSource source, Type backgroundType) { - BeatmapBackground last = null; + Graphics.Backgrounds.Background last = null; - setSourceMode(BackgroundSource.Beatmap); + setSourceMode(source); setSupporter(true); - AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground() as BeatmapBackground) != null); + AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == backgroundType); AddAssert("next doesn't load new background", () => screen.Next() == false); // doesn't really need to be checked but might as well. From 97204b6f278408f97fe3ab2c5cec289445c6810a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Jun 2021 22:26:15 +0200 Subject: [PATCH 0549/1621] Reduce unnecessary background changes via `IEquatable` implementation --- osu.Game/Graphics/Backgrounds/Background.cs | 12 +++++++++++- osu.Game/Graphics/Backgrounds/BeatmapBackground.cs | 9 +++++++++ osu.Game/Graphics/Backgrounds/SkinBackground.cs | 9 +++++++++ .../Screens/Backgrounds/BackgroundScreenDefault.cs | 11 +++++------ 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index c90b1e0e98..cfc1eb1806 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -14,7 +15,7 @@ namespace osu.Game.Graphics.Backgrounds /// /// A background which offers blurring via a on demand. /// - public class Background : CompositeDrawable + public class Background : CompositeDrawable, IEquatable { private const float blur_scale = 0.5f; @@ -71,5 +72,14 @@ namespace osu.Game.Graphics.Backgrounds bufferedContainer?.BlurTo(newBlurSigma * blur_scale, duration, easing); } + + public virtual bool Equals(Background other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return other.GetType() == GetType() + && other.textureName == textureName; + } } } diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs index 058d2ed0f9..e0c15dd52a 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs @@ -24,5 +24,14 @@ namespace osu.Game.Graphics.Backgrounds { Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName); } + + public override bool Equals(Background other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return other.GetType() == GetType() + && ((BeatmapBackground)other).Beatmap == Beatmap; + } } } diff --git a/osu.Game/Graphics/Backgrounds/SkinBackground.cs b/osu.Game/Graphics/Backgrounds/SkinBackground.cs index 8be017dc91..9266e7b17b 100644 --- a/osu.Game/Graphics/Backgrounds/SkinBackground.cs +++ b/osu.Game/Graphics/Backgrounds/SkinBackground.cs @@ -21,5 +21,14 @@ namespace osu.Game.Graphics.Backgrounds { Sprite.Texture = skin.GetTexture("menu-background") ?? Sprite.Texture; } + + public override bool Equals(Background other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return other.GetType() == GetType() + && ((SkinBackground)other).skin.SkinInfo.Equals(skin.SkinInfo); + } } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 46574ff8c8..81b15570d2 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -112,12 +112,6 @@ namespace osu.Game.Screens.Backgrounds newBackground = new BeatmapBackgroundWithStoryboard(beatmap.Value, getBackgroundTextureName()); newBackground ??= new BeatmapBackground(beatmap.Value, getBackgroundTextureName()); - // this method is called in many cases where the beatmap hasn't changed (ie. on screen transitions). - // if a background is already displayed for the requested beatmap, we don't want to load it again. - if (background?.GetType() == newBackground.GetType() && - (background as BeatmapBackground)?.Beatmap == beatmap.Value) - return background; - break; } @@ -127,6 +121,11 @@ namespace osu.Game.Screens.Backgrounds } } + // this method is called in many cases where the background might not necessarily need to change. + // if an equivalent background is currently being shown, we don't want to load it again. + if (newBackground?.Equals(background) == true) + return background; + newBackground ??= new Background(getBackgroundTextureName()); newBackground.Depth = currentDisplay; From 4707918c6afedf0cff2111dbbdfc2833aba75b11 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 9 Jun 2021 10:53:52 +0900 Subject: [PATCH 0550/1621] Fix hit circle animation when a replay is rewound --- osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs | 2 ++ osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs index b52dc749f0..fece3494e6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public class MainCirclePiece : CompositeDrawable { + public override bool RemoveCompletedTransforms => false; + private readonly CirclePiece circle; private readonly RingPiece ring; private readonly FlashPiece flash; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index 822dad8523..e5200ac248 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class LegacyMainCirclePiece : CompositeDrawable { + public override bool RemoveCompletedTransforms => false; + private readonly string priorityLookup; private readonly bool hasNumber; From 555ab8fccd61f3aff1ee9237e9da873d02a9b500 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 9 Jun 2021 12:31:30 +0900 Subject: [PATCH 0551/1621] Fix event not unregistered on dispose --- osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs index ae8c03dad1..df33bf52be 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs @@ -128,5 +128,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn); } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (drawableSpinner != null) + drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; + } } } From 249a8f259bb0ef55777d92531dfd5f32f3903317 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 13:44:26 +0900 Subject: [PATCH 0552/1621] Reword "unranked" to "not ranked" on beatmap overlay This will be replaced anyway once we start to consume osu-web translation strings. --- osu.Game/Overlays/BeatmapSet/Info.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index bac658b76e..dbe01ad27f 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.BeatmapSet public Info() { MetadataSection source, tags, genre, language; - OsuSpriteText unrankedPlaceholder; + OsuSpriteText notRankedPlaceholder; RelativeSizeAxes = Axes.X; Height = base_height; @@ -102,12 +102,12 @@ namespace osu.Game.Overlays.BeatmapSet RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = 20, Horizontal = 15 }, }, - unrankedPlaceholder = new OsuSpriteText + notRankedPlaceholder = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Alpha = 0, - Text = "Unranked beatmap", + Text = "This beatmap is not ranked", Font = OsuFont.GetFont(size: 12) }, }, @@ -124,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? string.Empty; var setHasLeaderboard = b.NewValue?.OnlineInfo?.Status > 0; successRate.Alpha = setHasLeaderboard ? 1 : 0; - unrankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1; + notRankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1; Height = setHasLeaderboard ? 270 : base_height; }; } From 7774344f0e40390c9c8c521fd1b721885d7ceb6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 13:45:09 +0900 Subject: [PATCH 0553/1621] Remove "Unranked" text from `ModDisplay` --- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 1 - .../OnlinePlay/FooterButtonFreeMods.cs | 1 - .../Multiplayer/MultiplayerMatchSubScreen.cs | 1 - .../Participants/ParticipantPanel.cs | 1 - .../Playlists/PlaylistsRoomSubScreen.cs | 1 - osu.Game/Screens/Play/HUD/ModDisplay.cs | 22 ++----------------- .../ContractedPanelMiddleContent.cs | 1 - .../Expanded/ExpandedPanelMiddleContent.cs | 1 - osu.Game/Screens/Select/FooterButtonMods.cs | 1 - 9 files changed, 2 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 38a9ace619..a3a61ccc36 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -202,7 +202,6 @@ namespace osu.Game.Screens.OnlinePlay Child = modDisplay = new ModDisplay { Scale = new Vector2(0.4f), - DisplayUnrankedText = false, ExpansionMode = ExpansionMode.AlwaysExpanded } } diff --git a/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs index a3cc383b67..834e82fcfd 100644 --- a/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs +++ b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs @@ -31,7 +31,6 @@ namespace osu.Game.Screens.OnlinePlay { Anchor = Anchor.Centre, Origin = Anchor.Centre, - DisplayUnrankedText = false, Scale = new Vector2(0.8f), ExpansionMode = ExpansionMode.AlwaysContracted, }); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 62ef70ed68..f9b3549f3c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -185,7 +185,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - DisplayUnrankedText = false, Current = UserMods, Scale = new Vector2(0.8f), }, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 5bef934e6a..f4a334e9d3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -139,7 +139,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants { Scale = new Vector2(0.5f), ExpansionMode = ExpansionMode.AlwaysContracted, - DisplayUnrankedText = false, } }, userStateDisplay = new StateDisplay diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 26ee21a2c3..092394446b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -175,7 +175,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - DisplayUnrankedText = false, Current = UserMods, Scale = new Vector2(0.8f), }, diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index cffdb21fb8..2f7ca74372 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -3,18 +3,15 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osuTK; -using osu.Game.Graphics.Containers; -using osu.Framework.Input.Events; -using osu.Game.Graphics; namespace osu.Game.Screens.Play.HUD { @@ -22,8 +19,6 @@ namespace osu.Game.Screens.Play.HUD { private const int fade_duration = 1000; - public bool DisplayUnrankedText = true; - public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover; private readonly Bindable> current = new Bindable>(); @@ -42,7 +37,6 @@ namespace osu.Game.Screens.Play.HUD } private readonly FillFlowContainer iconsContainer; - private readonly OsuSpriteText unrankedText; public ModDisplay() { @@ -63,13 +57,6 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, }, - unrankedText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = @"/ UNRANKED /", - Font = OsuFont.Numeric.With(size: 12) - } }, }; } @@ -102,11 +89,6 @@ namespace osu.Game.Screens.Play.HUD private void appearTransform() { - if (DisplayUnrankedText && Current.Value.Any(m => !m.Ranked)) - unrankedText.FadeInFromZero(fade_duration, Easing.OutQuint); - else - unrankedText.Hide(); - expand(); using (iconsContainer.BeginDelayedSequence(1200)) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 24f1116d0e..7e8dcdcfe0 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -137,7 +137,6 @@ namespace osu.Game.Screens.Ranking.Contracted Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, ExpansionMode = ExpansionMode.AlwaysExpanded, - DisplayUnrankedText = false, Current = { Value = score.Mods }, Scale = new Vector2(0.5f), } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 4895240314..f65aed3037 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -242,7 +242,6 @@ namespace osu.Game.Screens.Ranking.Expanded { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - DisplayUnrankedText = false, ExpansionMode = ExpansionMode.AlwaysExpanded, Scale = new Vector2(0.5f), Current = { Value = score.Mods } diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index b98b48a0c0..5bbca5ca1a 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -37,7 +37,6 @@ namespace osu.Game.Screens.Select { Anchor = Anchor.Centre, Origin = Anchor.Centre, - DisplayUnrankedText = false, Scale = new Vector2(0.8f), ExpansionMode = ExpansionMode.AlwaysContracted, }); From a87226ab10a241f52bb10129622d9e3420bc44fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 14:08:27 +0900 Subject: [PATCH 0554/1621] Remove obsoleted `DrawableJudgement` methods Undated, but change was made on 2020-11-18. --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index feeafb7151..8a57b4af91 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.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.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -32,18 +31,6 @@ namespace osu.Game.Rulesets.Judgements private readonly Container aboveHitObjectsContent; - /// - /// Duration of initial fade in. - /// - [Obsolete("Apply any animations manually via ApplyHitAnimations / ApplyMissAnimations. Defaults were moved inside skinned components.")] - protected virtual double FadeInDuration => 100; - - /// - /// Duration to wait until fade out begins. Defaults to . - /// - [Obsolete("Apply any animations manually via ApplyHitAnimations / ApplyMissAnimations. Defaults were moved inside skinned components.")] - protected virtual double FadeOutDelay => FadeInDuration; - /// /// Creates a drawable which visualises a . /// From f41e34ae2c840adadc16dffb04994b1da815a4ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 14:09:28 +0900 Subject: [PATCH 0555/1621] Remove more obsoleted members --- osu.Game/Graphics/Backgrounds/Triangles.cs | 6 ---- osu.Game/Rulesets/Judgements/Judgement.cs | 9 ----- .../Objects/Drawables/DrawableHitObject.cs | 36 ++----------------- 3 files changed, 2 insertions(+), 49 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 67cee883c8..269360c492 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -57,12 +57,6 @@ namespace osu.Game.Graphics.Backgrounds } } - /// - /// Whether we want to expire triangles as they exit our draw area completely. - /// - [Obsolete("Unused.")] // Can be removed 20210518 - protected virtual bool ExpireOffScreenTriangles => true; - /// /// Whether we should create new triangles as others expire. /// diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index be69db5ca8..fd576e9b9f 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.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.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -69,14 +68,6 @@ namespace osu.Game.Rulesets.Judgements ///
public double MaxHealthIncrease => HealthIncreaseFor(MaxResult); - /// - /// Retrieves the numeric score representation of a . - /// - /// The to find the numeric score representation for. - /// The numeric score representation of . - [Obsolete("Has no effect. Use ToNumericResult(HitResult) (standardised across all rulesets).")] // Can be made non-virtual 20210328 - protected virtual int NumericResultFor(HitResult result) => ToNumericResult(result); - /// /// Retrieves the numeric score representation of a . /// diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 6c688c1625..ef4fc1bc15 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -409,11 +409,6 @@ namespace osu.Game.Rulesets.Objects.Drawables using (BeginAbsoluteSequence(StateUpdateTime, true)) UpdateStartTimeStateTransforms(); -#pragma warning disable 618 - using (BeginAbsoluteSequence(StateUpdateTime + (Result?.TimeOffset ?? 0), true)) - UpdateStateTransforms(newState); -#pragma warning restore 618 - using (BeginAbsoluteSequence(HitStateUpdateTime, true)) UpdateHitStateTransforms(newState); @@ -447,7 +442,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// By default, this will fade in the object from zero with no duration. ///
/// - /// This is called once before every . This is to ensure a good state in the case + /// This is called once before every . This is to ensure a good state in the case /// the was negative and potentially altered the pre-hit transforms. /// protected virtual void UpdateInitialTransforms() @@ -455,16 +450,6 @@ namespace osu.Game.Rulesets.Objects.Drawables this.FadeInFromZero(); } - /// - /// Apply transforms based on the current . Previous states are automatically cleared. - /// In the case of a non-idle , and if was not set during this call, will be invoked. - /// - /// The new armed state. - [Obsolete("Use UpdateStartTimeStateTransforms and UpdateHitStateTransforms instead")] // Can be removed 20210504 - protected virtual void UpdateStateTransforms(ArmedState state) - { - } - /// /// Apply passive transforms at the 's StartTime. /// This is called each time changes. @@ -520,23 +505,6 @@ namespace osu.Game.Rulesets.Objects.Drawables AccentColour.Value = combo.GetComboColour(comboColours); } - /// - /// Called to retrieve the combo colour. Automatically assigned to . - /// Defaults to using to decide on a colour. - /// - /// - /// This will only be called if the implements . - /// - /// A list of combo colours provided by the beatmap or skin. Can be null if not available. - [Obsolete("Unused. Implement IHasComboInformation and IHasComboInformation.GetComboColour() on the HitObject model instead.")] // Can be removed 20210527 - protected virtual Color4 GetComboColour(IReadOnlyList comboColours) - { - if (!(HitObject is IHasComboInformation combo)) - throw new InvalidOperationException($"{nameof(HitObject)} must implement {nameof(IHasComboInformation)}"); - - return comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White; - } - /// /// Called when a change is made to the skin. /// @@ -630,7 +598,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The time at which state transforms should be applied that line up to 's StartTime. - /// This is used to offset calls to . + /// This is used to offset calls to . /// public double StateUpdateTime => HitObject.StartTime; From 62199a38a80716aa2617259b051f4d29996ab8d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 14:11:50 +0900 Subject: [PATCH 0556/1621] Add one missing obsoletion removal date --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ef4fc1bc15..8818e4c14a 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Applies a hit object to be represented by this . /// - [Obsolete("Use either overload of Apply that takes a single argument of type HitObject or HitObjectLifetimeEntry")] + [Obsolete("Use either overload of Apply that takes a single argument of type HitObject or HitObjectLifetimeEntry")] // Can be removed 20211021. public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry) { if (lifetimeEntry != null) From d0e9f8ef90b93ab1116f7a6fa91571defec2090e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 14:17:01 +0900 Subject: [PATCH 0557/1621] Replace and obsolete `Ranked` flag with `IsUserPlayable` --- .../Difficulty/CatchPerformanceCalculator.cs | 2 +- osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs | 1 - .../Difficulty/ManiaPerformanceCalculator.cs | 2 +- osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs | 1 - osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs | 1 - .../Difficulty/OsuPerformanceCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs | 1 - osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 1 - osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 1 - osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs | 1 - .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs | 1 - osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 2 +- osu.Game/Rulesets/Mods/Mod.cs | 8 ++++++-- osu.Game/Rulesets/Mods/ModAutoplay.cs | 2 ++ osu.Game/Rulesets/Mods/ModClassic.cs | 2 -- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 1 - osu.Game/Rulesets/Mods/ModEasy.cs | 1 - osu.Game/Rulesets/Mods/ModFlashlight.cs | 1 - osu.Game/Rulesets/Mods/ModHalfTime.cs | 1 - osu.Game/Rulesets/Mods/ModHidden.cs | 1 - osu.Game/Rulesets/Mods/ModNoFail.cs | 1 - osu.Game/Rulesets/Mods/ModPerfect.cs | 1 - osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 1 - 26 files changed, 13 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 6a3a16ed33..165dfb85e9 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty misses = Score.Statistics.GetOrDefault(HitResult.Miss); // Don't count scores made with supposedly unranked mods - if (mods.Any(m => !m.Ranked)) + if (mods.Any(m => !m.UserPlayable)) return 0; // We are heavily relying on aim in catch the beat diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index ced1900ba9..0dde6aa06e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -10,7 +10,6 @@ namespace osu.Game.Rulesets.Catch.Mods public class CatchModHardRock : ModHardRock, IApplicableToBeatmap { public override double ScoreMultiplier => 1.12; - public override bool Ranked => true; public void ApplyToBeatmap(IBeatmap beatmap) => CatchBeatmapProcessor.ApplyPositionOffsets(beatmap, this); } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 00bec18a45..d630f6b4a8 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); - if (mods.Any(m => !m.Ranked)) + if (mods.Any(m => !m.UserPlayable)) return 0; IEnumerable scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index 8fd5950dfb..050b302bd8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs @@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mania.Mods public abstract int KeyCount { get; } public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier - public override bool Ranked => true; public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter) { diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs index 078394b1d8..614ef76a3b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs @@ -24,8 +24,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override ModType Type => ModType.Conversion; - public override bool Ranked => false; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { var maniaRuleset = (DrawableManiaRuleset)drawableRuleset; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index 12f379bddb..cf404cc98e 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override ModType Type => ModType.Conversion; public override string Description => "Notes are flipped horizontally."; public override double ScoreMultiplier => 1; - public override bool Ranked => true; public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 44a9dd2f1f..71908e6693 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); // Don't count scores made with supposedly unranked mods - if (mods.Any(m => !m.Ranked)) + if (mods.Any(m => !m.UserPlayable)) return 0; // Custom multipliers for NoFail and SpunOut. diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 6841ecd23c..ebf6f9dda7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override IconUsage? Icon => FontAwesome.Solid.Adjust; public override ModType Type => ModType.DifficultyIncrease; - public override bool Ranked => false; - public override double ScoreMultiplier => 1.12; private DrawableOsuBlinds blinds; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index e0577dd464..16c166257a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModHardRock : ModHardRock, IApplicableToHitObject { public override double ScoreMultiplier => 1.06; - public override bool Ranked => true; public void ApplyToHitObject(HitObject hitObject) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index c282a919ea..97e3d82664 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModRandom : ModRandom, IApplicableToBeatmap { public override string Description => "It never gets boring!"; - public override bool Ranked => false; // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index f080e11933..b12d735474 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override string Description => @"Spinners will be automatically completed."; public override double ScoreMultiplier => 0.9; - public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) }; public void ApplyToDrawableHitObjects(IEnumerable drawables) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs index 3b16e9d2b7..789b06ddbe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -14,6 +14,5 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.System; - public override bool Ranked => true; } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 2d9b95ae88..5d82bd1f09 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); // Don't count scores made with supposedly unranked mods - if (mods.Any(m => !m.Ranked)) + if (mods.Any(m => !m.UserPlayable)) return 0; // Custom multipliers for NoFail and SpunOut. diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index a5a8b75f80..8437dfe52e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs @@ -9,7 +9,6 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModHardRock : ModHardRock { public override double ScoreMultiplier => 1.06; - public override bool Ranked => true; /// /// Multiplier factor added to the scrolling speed. diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 60fd520681..98662e5dea 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m))); + modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.UserPlayable).Select(m => new ModButton(m))); modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged); } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 7f48888abe..79d16013e3 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -108,10 +108,14 @@ namespace osu.Game.Rulesets.Mods public virtual bool HasImplementation => this is IApplicableMod; /// - /// Returns if this mod is ranked. + /// Whether this mod is playable by an end user. + /// Should be false for cases where the user is not interacting with the game (so it can be excluded from mutliplayer selection, for example). /// [JsonIgnore] - public virtual bool Ranked => false; + public virtual bool UserPlayable => true; + + [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to true.")] // Can be removed 20211009 + public virtual bool IsRanked => false; /// /// Whether this mod requires configuration to apply changes to the game. diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index b84b5671e1..4849d6ea36 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Mods public bool RestartOnFail => false; + public override bool UserPlayable => false; + public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) }; public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; diff --git a/osu.Game/Rulesets/Mods/ModClassic.cs b/osu.Game/Rulesets/Mods/ModClassic.cs index f1207ec188..1159955e11 100644 --- a/osu.Game/Rulesets/Mods/ModClassic.cs +++ b/osu.Game/Rulesets/Mods/ModClassic.cs @@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Mods public override string Description => "Feeling nostalgic?"; - public override bool Ranked => false; - public override ModType Type => ModType.Conversion; } } diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 152657da33..d12f48e973 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModDoubletime; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Zoooooooooom..."; - public override bool Ranked => true; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModHalfTime)).ToArray(); diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 1290e8136c..0f51e2a6d5 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModEasy; public override ModType Type => ModType.DifficultyReduction; public override double ScoreMultiplier => 0.5; - public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) }; public virtual void ReadFromDifficulty(BeatmapDifficulty difficulty) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 08f2ccb75c..7abae71273 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModFlashlight; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; - public override bool Ranked => true; internal ModFlashlight() { diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 203b88951c..c240cdbe6e 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModHalftime; public override ModType Type => ModType.DifficultyReduction; public override string Description => "Less zoom..."; - public override bool Ranked => true; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModDoubleTime)).ToArray(); diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 238612b3d2..5a8226115f 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Mods public override string Acronym => "HD"; public override IconUsage? Icon => OsuIcon.ModHidden; public override ModType Type => ModType.DifficultyIncrease; - public override bool Ranked => true; public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index c0f24e116a..abf67c2e2d 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyReduction; public override string Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; - public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModAutoplay) }; } } diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index d0b09b50f2..187a4d8e23 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Mods public override string Acronym => "PF"; public override IconUsage? Icon => OsuIcon.ModPerfect; public override ModType Type => ModType.DifficultyIncrease; - public override bool Ranked => true; public override double ScoreMultiplier => 1; public override string Description => "SS or quit."; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 617ae38feb..1abd353d20 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Miss and fail."; public override double ScoreMultiplier => 1; - public override bool Ranked => true; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray(); From b8df3fff9e26a4454240c2c562c6598d42cdd9bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 14:20:01 +0900 Subject: [PATCH 0558/1621] Fix incorrect method referenced in xmldco Co-authored-by: ekrctb <32995012+ekrctb@users.noreply.github.com> --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 8818e4c14a..5fd2b2493e 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -598,7 +598,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The time at which state transforms should be applied that line up to 's StartTime. - /// This is used to offset calls to . + /// This is used to offset calls to . /// public double StateUpdateTime => HitObject.StartTime; From b754c5239253341758b166c3872d983e7e115290 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 14:32:48 +0900 Subject: [PATCH 0559/1621] Update `ModAutoplay` matching to use new `UserPlayable` flag instead --- osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs | 2 +- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 2 +- osu.Game/Screens/Play/SubmittingPlayer.cs | 4 ++-- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 66262e7dc4..5e2e9fd087 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay public new Func IsValidMod { get => base.IsValidMod; - set => base.IsValidMod = m => m.HasImplementation && !(m is ModAutoplay) && value(m); + set => base.IsValidMod = m => m.HasImplementation && m.UserPlayable && value(m); } public FreeModSelectOverlay() diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 3e7e557aad..2c46f76737 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -161,7 +161,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// The to check. /// Whether is a valid mod for online play. - protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && !ModUtils.FlattenMod(mod).Any(m => m is ModAutoplay); + protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && ModUtils.FlattenMod(mod).All(m => m.UserPlayable); /// /// Checks whether a given is valid for per-player free-mod selection. diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 23b9037244..ea53d03fcb 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -44,9 +44,9 @@ namespace osu.Game.Screens.Play // Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request. var tcs = new TaskCompletionSource(); - if (Mods.Value.Any(m => m is ModAutoplay)) + if (Mods.Value.Any(m => !m.UserPlayable)) { - handleTokenFailure(new InvalidOperationException("Autoplay loaded.")); + handleTokenFailure(new InvalidOperationException("Non-user playable mod selected.")); return false; } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index a0ea27b640..3ea764ed58 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -153,7 +153,7 @@ namespace osu.Game.Screens.Ranking if (Score != null) { // only show flair / animation when arriving after watching a play that isn't autoplay. - bool shouldFlair = player != null && !Score.Mods.Any(m => m is ModAutoplay); + bool shouldFlair = player != null && Score.Mods.All(m => m.UserPlayable); ScorePanelList.AddScore(Score, shouldFlair); From 85abee5fc703914621aab0d25611cff9c3f95446 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 14:33:34 +0900 Subject: [PATCH 0560/1621] Remove difficulty calculator exceptions I don't think there's any reason difficulty calculators shouldn't be able to calculate for autoplays. --- .../Difficulty/CatchPerformanceCalculator.cs | 4 ---- .../Difficulty/ManiaPerformanceCalculator.cs | 3 --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ---- .../Difficulty/TaikoPerformanceCalculator.cs | 4 ---- 4 files changed, 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 165dfb85e9..fdd6ac0857 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -39,10 +39,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss); misses = Score.Statistics.GetOrDefault(HitResult.Miss); - // Don't count scores made with supposedly unranked mods - if (mods.Any(m => !m.UserPlayable)) - return 0; - // We are heavily relying on aim in catch the beat double value = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0; diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index d630f6b4a8..405ac56e94 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -44,9 +44,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); - if (mods.Any(m => !m.UserPlayable)) - return 0; - IEnumerable scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease); double scoreMultiplier = 1.0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 71908e6693..749d7d1b41 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -41,10 +41,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); - // Don't count scores made with supposedly unranked mods - if (mods.Any(m => !m.UserPlayable)) - return 0; - // Custom multipliers for NoFail and SpunOut. double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 5d82bd1f09..6117ed1673 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -36,10 +36,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); - // Don't count scores made with supposedly unranked mods - if (mods.Any(m => !m.UserPlayable)) - return 0; - // Custom multipliers for NoFail and SpunOut. double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things From 5487012060e239cbcfca3028adc71f5a03cec6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 9 Jun 2021 07:48:16 +0200 Subject: [PATCH 0561/1621] Add test coverage for default skin background cycling --- .../TestSceneBackgroundScreenDefault.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index 135998695b..f7d42a2ee6 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Backgrounds; using osu.Game.Online.API; using osu.Game.Screens; using osu.Game.Screens.Backgrounds; +using osu.Game.Skinning; using osu.Game.Users; namespace osu.Game.Tests.Visual.Background @@ -24,6 +25,9 @@ namespace osu.Game.Tests.Visual.Background private Graphics.Backgrounds.Background getCurrentBackground() => screen.ChildrenOfType().FirstOrDefault(); + [Resolved] + private SkinManager skins { get; set; } + [Resolved] private OsuConfigManager config { get; set; } @@ -47,6 +51,9 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("is storyboard background", () => getCurrentBackground() is BeatmapBackgroundWithStoryboard); setSourceMode(BackgroundSource.Skin); + AddUntilStep("is default background", () => getCurrentBackground().GetType() == typeof(Graphics.Backgrounds.Background)); + + setCustomSkin(); AddUntilStep("is skin background", () => getCurrentBackground() is SkinBackground); } @@ -74,6 +81,8 @@ namespace osu.Game.Tests.Visual.Background setSourceMode(source); setSupporter(true); + if (source == BackgroundSource.Skin) + setCustomSkin(); AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == backgroundType); AddAssert("next doesn't load new background", () => screen.Next() == false); @@ -83,6 +92,23 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("ensure same background instance", () => last == getCurrentBackground()); } + [Test] + public void TestBackgroundCyclingOnDefaultSkin([Values] bool supporter) + { + Graphics.Backgrounds.Background last = null; + + setSourceMode(BackgroundSource.Skin); + setSupporter(supporter); + setDefaultSkin(); + + AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background)); + AddAssert("next cycles background", () => screen.Next()); + + // doesn't really need to be checked but might as well. + AddWaitStep("wait a bit", 5); + AddUntilStep("ensure different background instance", () => last != getCurrentBackground()); + } + private void setSourceMode(BackgroundSource source) => AddStep($"set background mode to {source}", () => config.SetValue(OsuSetting.MenuBackgroundSource, source)); @@ -92,5 +118,16 @@ namespace osu.Game.Tests.Visual.Background IsSupporter = isSupporter, Id = API.LocalUser.Value.Id + 1, }); + + private void setCustomSkin() + { + // feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin. + AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo { ID = 5 }); + } + + private void setDefaultSkin() => AddStep("set default skin", () => skins.CurrentSkinInfo.SetDefault()); + + [TearDownSteps] + public void TearDown() => setDefaultSkin(); } } From a801a9a14d7b52fbafb3f88a43180f53af53552a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 9 Jun 2021 07:59:47 +0200 Subject: [PATCH 0562/1621] Ensure background rotation on default skins --- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 81b15570d2..f0c90cc409 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -116,6 +116,10 @@ namespace osu.Game.Screens.Backgrounds } case BackgroundSource.Skin: + // default skins should use the default background rotation, which won't be the case if a SkinBackground is created for them. + if (skin.Value is DefaultSkin || skin.Value is DefaultLegacySkin) + break; + newBackground = new SkinBackground(skin.Value, getBackgroundTextureName()); break; } From d248bbd4c8dff39fafc7c316f37033d6a563e490 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 15:00:55 +0900 Subject: [PATCH 0563/1621] Use candidate skin for mania skin key lookup rather than `this` --- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 8aa0c85433..962a13ebea 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { isLegacySkin = new Lazy(() => FindProvider(s => s.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null) != null); hasKeyTexture = new Lazy(() => FindProvider(s => s.GetAnimation( - this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value + s.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value ?? "mania-key1", true, true) != null) != null); } From e5deecf459b279ef3e16137a5d4984793e0c08fe Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 9 Jun 2021 15:47:23 +0900 Subject: [PATCH 0564/1621] Check skin version for legacy catcher sprite --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 6a9a3c43a2..b23011f1a3 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -66,10 +66,14 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy return null; case CatchSkinComponents.Catcher: - // New elements will be ignored when the old element exists. - if (GetTexture(@"fruit-ryuuta") != null || - GetTexture(@"fruit-ryuuta-0") != null) - return new LegacyCatcherOld(); + var version = Source.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value ?? 1; + + if (version < 2.3m) + { + if (GetTexture(@"fruit-ryuuta") != null || + GetTexture(@"fruit-ryuuta-0") != null) + return new LegacyCatcherOld(); + } if (GetTexture(@"fruit-catcher-idle") != null || GetTexture(@"fruit-catcher-idle-0") != null) From 610cdaea98bb1fb9720035cfdbdaa113c542bed8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 9 Jun 2021 16:14:55 +0900 Subject: [PATCH 0565/1621] Fix circle piece animation is sometimes not playing when a replay is rewound --- .../Skinning/Default/MainCirclePiece.cs | 21 +++++++++++-------- .../Skinning/Legacy/LegacyMainCirclePiece.cs | 17 ++++++++++----- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs index fece3494e6..d7ebe9333d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs @@ -15,8 +15,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public class MainCirclePiece : CompositeDrawable { - public override bool RemoveCompletedTransforms => false; - private readonly CirclePiece circle; private readonly RingPiece ring; private readonly FlashPiece flash; @@ -44,7 +42,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private readonly IBindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); - private readonly IBindable armedState = new Bindable(); [Resolved] private DrawableHitObject drawableObject { get; set; } @@ -56,7 +53,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default accentColour.BindTo(drawableObject.AccentColour); indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable); - armedState.BindTo(drawableObject.State); } protected override void LoadComplete() @@ -72,19 +68,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true); - armedState.BindValueChanged(animate, true); + drawableObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(drawableObject, drawableObject.State.Value); } - private void animate(ValueChangedEvent state) + private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { - ClearTransforms(true); - using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) glow.FadeOut(400); using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { - switch (state.NewValue) + switch (state) { case ArmedState.Hit: const double flash_in = 40; @@ -111,5 +106,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default } } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (drawableObject != null) + drawableObject.ApplyCustomUpdateState -= updateStateTransforms; + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index e5200ac248..bb7a335efe 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -41,7 +41,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private readonly Bindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); - private readonly IBindable armedState = new Bindable(); [Resolved] private DrawableHitObject drawableObject { get; set; } @@ -116,7 +115,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy accentColour.BindTo(drawableObject.AccentColour); indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable); - armedState.BindTo(drawableObject.State); Texture getTextureWithFallback(string name) { @@ -142,10 +140,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (hasNumber) indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); - armedState.BindValueChanged(animate, true); + drawableObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(drawableObject, drawableObject.State.Value); } - private void animate(ValueChangedEvent state) + private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { const double legacy_fade_duration = 240; @@ -153,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { - switch (state.NewValue) + switch (state) { case ArmedState.Hit: circleSprites.FadeOut(legacy_fade_duration, Easing.Out); @@ -178,5 +177,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (drawableObject != null) + drawableObject.ApplyCustomUpdateState -= updateStateTransforms; + } } } From 38fc9347bec98496ff03a8b6f72fb0f5d18c17d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 16:13:00 +0900 Subject: [PATCH 0566/1621] Add failing test coverage for beatmap skin disable --- .../TestSceneBeatmapSkinLookupDisables.cs | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs new file mode 100644 index 0000000000..4f3924602f --- /dev/null +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs @@ -0,0 +1,116 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; +using osu.Game.Audio; +using osu.Game.Configuration; +using osu.Game.Rulesets.Osu; +using osu.Game.Skinning; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Skins +{ + [TestFixture] + [HeadlessTest] + public class TestSceneBeatmapSkinLookupDisables : OsuTestScene + { + private UserSkinSource userSource; + private BeatmapSkinSource beatmapSource; + private SkinRequester requester; + + [Resolved] + private OsuConfigManager config { get; set; } + + [SetUp] + public void SetUp() => Schedule(() => + { + Add(new SkinProvidingContainer(userSource = new UserSkinSource()) + .WithChild(new BeatmapSkinProvidingContainer(beatmapSource = new BeatmapSkinSource()) + .WithChild(requester = new SkinRequester()))); + }); + + [TestCase(false)] + [TestCase(true)] + public void TestDrawableLookup(bool allowBeatmapLookups) + { + AddStep($"Set beatmap skin enabled to {allowBeatmapLookups}", () => config.SetValue(OsuSetting.BeatmapSkins, allowBeatmapLookups)); + + string expected = allowBeatmapLookups ? "beatmap" : "user"; + + AddAssert($"Check lookup is from {expected}", () => requester.GetDrawableComponent(new TestSkinComponent())?.Name == expected); + } + + [TestCase(false)] + [TestCase(true)] + public void TestProviderLookup(bool allowBeatmapLookups) + { + AddStep($"Set beatmap skin enabled to {allowBeatmapLookups}", () => config.SetValue(OsuSetting.BeatmapSkins, allowBeatmapLookups)); + + ISkin expected() => allowBeatmapLookups ? (ISkin)beatmapSource : userSource; + + AddAssert($"Check lookup is from correct source", () => requester.FindProvider(s => s.GetDrawableComponent(new TestSkinComponent()) != null) == expected()); + } + + public class UserSkinSource : LegacySkin + { + public UserSkinSource() + : base(new SkinInfo(), null, null, string.Empty) + { + } + + public override Drawable GetDrawableComponent(ISkinComponent component) + { + return new Container { Name = "user" }; + } + } + + public class BeatmapSkinSource : LegacyBeatmapSkin + { + public BeatmapSkinSource() + : base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null, null) + { + } + + public override Drawable GetDrawableComponent(ISkinComponent component) + { + return new Container { Name = "beatmap" }; + } + } + + public class SkinRequester : Drawable, ISkin + { + private ISkinSource skin; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + this.skin = skin; + } + + public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component); + + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => skin.GetTexture(componentName, wrapModeS, wrapModeT); + + public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); + + public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); + + public ISkin FindProvider(Func lookupFunction) => skin.FindProvider(lookupFunction); + } + + private class TestSkinComponent : ISkinComponent + { + public string LookupName => string.Empty; + } + } +} From 448e4e7ee53fea46c6bf28a77d3ae13db07dcef5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 15:25:42 +0900 Subject: [PATCH 0567/1621] Fix `FindProvider` calls on `SkinProvidingContainer` not considering disable flags Closes #13394. --- osu.Game/Skinning/SkinProvidingContainer.cs | 71 +++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 0e16cf43ee..5e91a280e7 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -27,6 +27,8 @@ namespace osu.Game.Skinning [CanBeNull] private ISkinSource fallbackSource; + private readonly NoFallbackProxy noFallbackLookupProxy; + protected virtual bool AllowDrawableLookup(ISkinComponent component) => true; protected virtual bool AllowTextureLookup(string componentName) => true; @@ -42,6 +44,8 @@ namespace osu.Game.Skinning this.skin = skin; RelativeSizeAxes = Axes.Both; + + noFallbackLookupProxy = new NoFallbackProxy(this); } public ISkin FindProvider(Func lookupFunction) @@ -53,7 +57,8 @@ namespace osu.Game.Skinning } else if (skin != null) { - if (lookupFunction(skin)) + // a proxy must be used here to correctly pass through the "Allow" checks without implicitly falling back to the fallbackSource. + if (lookupFunction(noFallbackLookupProxy)) return skin; } @@ -61,46 +66,70 @@ namespace osu.Game.Skinning } public Drawable GetDrawableComponent(ISkinComponent component) + => GetDrawableComponent(component, true); + + public Drawable GetDrawableComponent(ISkinComponent component, bool fallback) { Drawable sourceDrawable; if (AllowDrawableLookup(component) && (sourceDrawable = 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) { Texture sourceTexture; if (AllowTextureLookup(componentName) && (sourceTexture = 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) { ISample sourceChannel; if (AllowSampleLookup(sampleInfo) && (sourceChannel = skin?.GetSample(sampleInfo)) != null) return sourceChannel; + if (!fallback) + return null; + return fallbackSource?.GetSample(sampleInfo); } public IBindable GetConfig(TLookup lookup) + => GetConfig(lookup, true); + + public IBindable GetConfig(TLookup lookup, bool fallback) { if (skin != null) { if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) - return lookupWithFallback(lookup, AllowColourLookup); + return lookupWithFallback(lookup, AllowColourLookup, fallback); - return lookupWithFallback(lookup, AllowConfigurationLookup); + return lookupWithFallback(lookup, AllowConfigurationLookup, fallback); } + if (!fallback) + return null; + return fallbackSource?.GetConfig(lookup); } - private IBindable lookupWithFallback(TLookup lookup, bool canUseSkinLookup) + private IBindable lookupWithFallback(TLookup lookup, bool canUseSkinLookup, bool canUseFallback) { if (canUseSkinLookup) { @@ -109,6 +138,9 @@ namespace osu.Game.Skinning return bindable; } + if (!canUseFallback) + return null; + return fallbackSource?.GetConfig(lookup); } @@ -137,5 +169,36 @@ namespace osu.Game.Skinning if (fallbackSource != null) fallbackSource.SourceChanged -= TriggerSourceChanged; } + + private class NoFallbackProxy : ISkinSource + { + private readonly SkinProvidingContainer provider; + + public NoFallbackProxy(SkinProvidingContainer provider) + { + 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; + } + + public ISkin FindProvider(Func lookupFunction) => + provider.FindProvider(lookupFunction); + } } } From 020c63017ece8c1d804fff9268b8d9ef41575b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 9 Jun 2021 09:21:02 +0200 Subject: [PATCH 0568/1621] Fix inspectcode issues --- osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs | 1 - osu.Game/Screens/Play/SubmittingPlayer.cs | 1 - osu.Game/Screens/Ranking/ResultsScreen.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs index 789b06ddbe..7276cc753c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -13,6 +13,5 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override ModType Type => ModType.System; - } } diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index ea53d03fcb..b843915a7c 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -10,7 +10,6 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Rooms; -using osu.Game.Rulesets.Mods; using osu.Game.Scoring; namespace osu.Game.Screens.Play diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 3ea764ed58..3f9a74d5d0 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -17,7 +17,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; 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; From 5418e895ae93b31f9515e7100a4a8136cd04756f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 9 Jun 2021 16:50:13 +0900 Subject: [PATCH 0569/1621] Remove useless `ClearTransforms` The transforms are cleared by DHO before `ApplyCustomUpdateState` is invoked. --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index bb7a335efe..7a210324d7 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -148,8 +148,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { const double legacy_fade_duration = 240; - ClearTransforms(true); - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { switch (state) From a7ef0173e9a20f8827e2b9ede18221985fca0a3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 17:07:28 +0900 Subject: [PATCH 0570/1621] Add safety to ensure background is correct tint when entering gameplay --- osu.Game/Screens/Play/Player.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 94e67107c9..f9036780aa 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -32,6 +32,7 @@ using osu.Game.Scoring.Legacy; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; +using osuTK.Graphics; namespace osu.Game.Screens.Play { @@ -856,6 +857,7 @@ namespace osu.Game.Screens.Play { b.IgnoreUserSettings.Value = false; b.BlurAmount.Value = 0; + b.FadeColour(Color4.White, 250); // bind component bindables. b.IsBreakTime.BindTo(breakTracker.IsBreakTime); From 258d05d1e0c8029cb8a28773804f828b0570f599 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 17:17:39 +0900 Subject: [PATCH 0571/1621] Ensure `PlayerLoader` restores the background colour to its own value on resume --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index ce580e2b53..5f6b4ca2b0 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -184,8 +184,6 @@ namespace osu.Game.Screens.Play { if (epilepsyWarning != null) epilepsyWarning.DimmableBackground = b; - - b?.FadeColour(Color4.White, 800, Easing.OutQuint); }); Beatmap.Value.Track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment); @@ -334,6 +332,8 @@ namespace osu.Game.Screens.Play content.FadeInFromZero(400); content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); + + ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); } private void contentOut() From 7b0c5e9d32528db538ee66775d40e96c2ca2b4d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 17:18:52 +0900 Subject: [PATCH 0572/1621] Fix results screen changing applied colour to background on exit The general rule is that screens should only apply colours and the likes on enter / resume, and leave the outwards transition to whatever screen is coming next. --- osu.Game/Screens/Ranking/ResultsScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index a0ea27b640..e460ee77f4 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -13,6 +13,7 @@ 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; using osu.Game.Input.Bindings; @@ -257,7 +258,7 @@ namespace osu.Game.Screens.Ranking ApplyToBackground(b => { b.BlurAmount.Value = BACKGROUND_BLUR; - b.FadeTo(0.5f, 250); + b.FadeColour(OsuColour.Gray(0.5f), 250); }); bottomPanel.FadeTo(1, 250); @@ -265,7 +266,6 @@ namespace osu.Game.Screens.Ranking public override bool OnExiting(IScreen next) { - ApplyToBackground(b => b.FadeTo(1, 250)); return base.OnExiting(next); } From a65b76bdbf3a360c7a9cb3c9d2f53dbbae35ad65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 17:19:36 +0900 Subject: [PATCH 0573/1621] Add a simple fade to the results screen Stops it from immediately disappearing. --- osu.Game/Screens/Ranking/ResultsScreen.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index e460ee77f4..c44ce63ccb 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -266,8 +266,11 @@ namespace osu.Game.Screens.Ranking public override bool OnExiting(IScreen next) { + if (base.OnExiting(next)) + return true; - return base.OnExiting(next); + this.FadeOut(100); + return false; } public override bool OnBackButton() From 47eeab34e182def8eac2dd5b14bf3ea1060e2184 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 17:34:47 +0900 Subject: [PATCH 0574/1621] Remove redundant string interpolation --- osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs index 4f3924602f..71544e94f3 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Skins ISkin expected() => allowBeatmapLookups ? (ISkin)beatmapSource : userSource; - AddAssert($"Check lookup is from correct source", () => requester.FindProvider(s => s.GetDrawableComponent(new TestSkinComponent()) != null) == expected()); + AddAssert("Check lookup is from correct source", () => requester.FindProvider(s => s.GetDrawableComponent(new TestSkinComponent()) != null) == expected()); } public class UserSkinSource : LegacySkin From 2438c20d63e8aa9ad3eb0d6a09a99a11e03bbc13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 17:56:07 +0900 Subject: [PATCH 0575/1621] Fix `SourceChanged` not being correctly forwarded through `LegacySkinTransformer` --- osu.Game/Skinning/LegacySkinTransformer.cs | 4 ++-- osu.Game/Skinning/SkinProvidingContainer.cs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 651fdddb1b..613b0218f2 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -53,8 +53,8 @@ namespace osu.Game.Skinning public event Action SourceChanged { - add { throw new NotSupportedException(); } - remove { } + add => Source.SourceChanged += value; + remove => Source.SourceChanged -= value; } } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 5e91a280e7..b1929aac6f 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -46,6 +46,9 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; noFallbackLookupProxy = new NoFallbackProxy(this); + + if (skin is ISkinSource source) + source.SourceChanged += TriggerSourceChanged; } public ISkin FindProvider(Func lookupFunction) @@ -168,6 +171,9 @@ namespace osu.Game.Skinning if (fallbackSource != null) fallbackSource.SourceChanged -= TriggerSourceChanged; + + if (skin is ISkinSource source) + source.SourceChanged -= TriggerSourceChanged; } private class NoFallbackProxy : ISkinSource From 0cf7c56e7ee51ed68310208cd8470e38eaf2f751 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 18:51:34 +0900 Subject: [PATCH 0576/1621] Add fallback lookup support for `DefaultSkin` --- osu.Game/Skinning/SkinManager.cs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 48d6b9254f..9e274227a2 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -50,6 +50,8 @@ namespace osu.Game.Skinning private readonly Skin defaultLegacySkin; + private readonly Skin defaultSkin; + public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, IResourceStore resources, AudioManager audio) : base(storage, contextFactory, new SkinStore(contextFactory, storage), host) { @@ -58,10 +60,11 @@ namespace osu.Game.Skinning this.resources = resources; defaultLegacySkin = new DefaultLegacySkin(this); + defaultSkin = new DefaultSkin(this); CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue); - CurrentSkin.Value = new DefaultSkin(this); + CurrentSkin.Value = defaultSkin; CurrentSkin.ValueChanged += skin => { if (skin.NewValue.SkinInfo != CurrentSkinInfo.Value) @@ -226,24 +229,26 @@ namespace osu.Game.Skinning if (CurrentSkin.Value is LegacySkin && lookupFunction(defaultLegacySkin)) return defaultLegacySkin; + if (lookupFunction(defaultSkin)) + return defaultSkin; + return null; } - private T lookupWithFallback(Func func) + private T lookupWithFallback(Func lookupFunction) where T : class { - var selectedSkin = func(CurrentSkin.Value); - - if (selectedSkin != null) - return selectedSkin; + 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) - return func(defaultLegacySkin); + if (CurrentSkin.Value is LegacySkin && lookupFunction(defaultLegacySkin) is T legacySourced) + return legacySourced; - return null; + // Finally fall back to the (non-legacy) default. + return lookupFunction(defaultSkin); } #region IResourceStorageProvider From 330bb7cb450f346b6cbb64a2a4f262c61624090d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 18:15:45 +0900 Subject: [PATCH 0577/1621] 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 0578/1621] 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 0579/1621] 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 0580/1621] 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 0581/1621] 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 0582/1621] 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 0583/1621] 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 0584/1621] 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 0585/1621] 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 0586/1621] 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 f113c095ce55640dabac6b1c4ffc956b28a39839 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Jun 2021 20:29:06 +0900 Subject: [PATCH 0587/1621] 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 395470824f..cfa3bc836f 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 9ecab1ee48..be10764c6a 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 e66f125985..751f60912a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From cf40282f1f30fb2d7d7a9c588157a0429b967e43 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 9 Jun 2021 13:34:42 +0300 Subject: [PATCH 0588/1621] Convert `LegacySkinTransformer`s to accept raw `ISkin`s rather than a full `ISkinSource` --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../Legacy/CatchLegacySkinTransformer.cs | 14 +++--- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Legacy/ManiaLegacySkinTransformer.cs | 32 ++++++------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Legacy/OsuLegacySkinTransformer.cs | 47 +++++++------------ .../Legacy/TaikoLegacySkinTransformer.cs | 20 +++----- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 6 +-- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Skinning/LegacySkinTransformer.cs | 25 ++++------ 11 files changed, 61 insertions(+), 93 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 23ce444560..ec4c5dfe4c 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Catch public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); - public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source); + public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin); public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score); diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 8c9e602cd4..9be47b3550 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy /// private bool providesComboCounter => this.HasFont(LegacyFont.Combo); - public CatchLegacySkinTransformer(ISkinSource source) - : base(source) + public CatchLegacySkinTransformer(ISkin skin) + : base(skin) { } @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (targetComponent.Target) { case SkinnableTarget.MainHUDComponents: - var components = Source.GetDrawableComponent(component) as SkinnableTargetComponentsContainer; + var components = Skin.GetDrawableComponent(component) as SkinnableTargetComponentsContainer; if (providesComboCounter && components != null) { @@ -79,13 +79,13 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy case CatchSkinComponents.CatchComboCounter: if (providesComboCounter) - return new LegacyCatchComboCounter(Source); + return new LegacyCatchComboCounter(Skin); return null; } } - return Source.GetDrawableComponent(component); + return Skin.GetDrawableComponent(component); } public override IBindable GetConfig(TLookup lookup) @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (lookup) { case CatchSkinColour colour: - var result = (Bindable)Source.GetConfig(new SkinCustomColourLookup(colour)); + var result = (Bindable)Skin.GetConfig(new SkinCustomColourLookup(colour)); if (result == null) return null; @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy return (IBindable)result; } - return Source.GetConfig(lookup); + return Skin.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index fbb9b3c466..fe736766d9 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mania public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this); - public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new ManiaLegacySkinTransformer(source, beatmap); + public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new ManiaLegacySkinTransformer(skin, beatmap); public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 962a13ebea..7d4d303bc9 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -50,29 +50,25 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { HitResult.Miss, "mania-hit0" } }; - private Lazy isLegacySkin; + private readonly Lazy isLegacySkin; /// /// Whether texture for the keys exists. /// Used to determine if the mania ruleset is skinned. /// - private Lazy hasKeyTexture; + private readonly Lazy hasKeyTexture; - public ManiaLegacySkinTransformer(ISkinSource source, IBeatmap beatmap) - : base(source) + public ManiaLegacySkinTransformer(ISkin skin, IBeatmap beatmap) + : base(skin) { this.beatmap = (ManiaBeatmap)beatmap; - Source.SourceChanged += sourceChanged; - sourceChanged(); - } - - private void sourceChanged() - { - isLegacySkin = new Lazy(() => FindProvider(s => s.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null) != null); - hasKeyTexture = new Lazy(() => FindProvider(s => s.GetAnimation( - s.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value - ?? "mania-key1", true, true) != null) != null); + isLegacySkin = new Lazy(() => skin.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; + }); } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -125,7 +121,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy break; } - return Source.GetDrawableComponent(component); + return Skin.GetDrawableComponent(component); } private Drawable getResult(HitResult result) @@ -146,15 +142,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered) return new SampleVirtual(); - return Source.GetSample(sampleInfo); + return Skin.GetSample(sampleInfo); } public override IBindable GetConfig(TLookup lookup) { if (lookup is ManiaSkinConfigurationLookup maniaLookup) - return Source.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn)); + return Skin.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn)); - return Source.GetConfig(lookup); + return Skin.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index b50d3ad2b4..69e22dc45d 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -218,7 +218,7 @@ namespace osu.Game.Rulesets.Osu public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this); - public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new OsuLegacySkinTransformer(source); + public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new OsuLegacySkinTransformer(skin); public int LegacyID => 0; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 3267b48ebf..3ad3b7d30b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class OsuLegacySkinTransformer : LegacySkinTransformer { - private Lazy hasHitCircle; + private readonly Lazy hasHitCircle; /// /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. @@ -20,16 +20,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy /// public const float LEGACY_CIRCLE_RADIUS = 64 - 5; - public OsuLegacySkinTransformer(ISkinSource source) - : base(source) + public OsuLegacySkinTransformer(ISkin skin) + : base(skin) { - Source.SourceChanged += sourceChanged; - sourceChanged(); - } - - private void sourceChanged() - { - hasHitCircle = new Lazy(() => FindProvider(s => s.GetTexture("hitcircle") != null) != null); + hasHitCircle = new Lazy(() => Skin.GetTexture("hitcircle") != null); } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -49,16 +43,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return followCircle; case OsuSkinComponents.SliderBall: - // specular and nd layers must come from the same source as the ball texure. - var ballProvider = Source.FindProvider(s => s.GetTexture("sliderb") != null || s.GetTexture("sliderb0") != null); - - var sliderBallContent = ballProvider.GetAnimation("sliderb", true, true, animationSeparator: ""); + var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: ""); // todo: slider ball has a custom frame delay based on velocity // Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME); if (sliderBallContent != null) - return new LegacySliderBall(sliderBallContent, ballProvider); + return new LegacySliderBall(sliderBallContent, this); return null; @@ -87,18 +78,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; case OsuSkinComponents.Cursor: - var cursorProvider = Source.FindProvider(s => s.GetTexture("cursor") != null); - - if (cursorProvider != null) - return new LegacyCursor(cursorProvider); + if (GetTexture("cursor") != null) + return new LegacyCursor(this); return null; case OsuSkinComponents.CursorTrail: - var trailProvider = Source.FindProvider(s => s.GetTexture("cursortrail") != null); - - if (trailProvider != null) - return new LegacyCursorTrail(trailProvider); + if (GetTexture("cursortrail") != null) + return new LegacyCursorTrail(this); return null; @@ -113,9 +100,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy }; case OsuSkinComponents.SpinnerBody: - bool hasBackground = Source.GetTexture("spinner-background") != null; + bool hasBackground = GetTexture("spinner-background") != null; - if (Source.GetTexture("spinner-top") != null && !hasBackground) + if (GetTexture("spinner-top") != null && !hasBackground) return new LegacyNewStyleSpinner(); else if (hasBackground) return new LegacyOldStyleSpinner(); @@ -124,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } } - return Source.GetDrawableComponent(component); + return Skin.GetDrawableComponent(component); } public override IBindable GetConfig(TLookup lookup) @@ -132,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy switch (lookup) { case OsuSkinColour colour: - return Source.GetConfig(new SkinCustomColourLookup(colour)); + return Skin.GetConfig(new SkinCustomColourLookup(colour)); case OsuSkinConfiguration osuLookup: switch (osuLookup) @@ -146,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 Source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? - Source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); + return Skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? + Skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; } - return Source.GetConfig(lookup); + return Skin.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index 7ce0f6b93b..0122f9a1cd 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -15,18 +15,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { public class TaikoLegacySkinTransformer : LegacySkinTransformer { - private Lazy hasExplosion; + private readonly Lazy hasExplosion; - public TaikoLegacySkinTransformer(ISkinSource source) - : base(source) + public TaikoLegacySkinTransformer(ISkin skin) + : base(skin) { - Source.SourceChanged += sourceChanged; - sourceChanged(); - } - - private void sourceChanged() - { - hasExplosion = new Lazy(() => Source.GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null); + hasExplosion = new Lazy(() => Skin.GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null); } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -132,7 +126,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy } } - return Source.GetDrawableComponent(component); + return Skin.GetDrawableComponent(component); } private string getHitName(TaikoSkinComponents component) @@ -155,12 +149,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public override ISample GetSample(ISampleInfo sampleInfo) { if (sampleInfo is HitSampleInfo hitSampleInfo) - return Source.GetSample(new LegacyTaikoSampleInfo(hitSampleInfo)); + return Skin.GetSample(new LegacyTaikoSampleInfo(hitSampleInfo)); return base.GetSample(sampleInfo); } - public override IBindable GetConfig(TLookup lookup) => Source.GetConfig(lookup); + public override IBindable GetConfig(TLookup lookup) => Skin.GetConfig(lookup); private class LegacyTaikoSampleInfo : HitSampleInfo { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 5854d4770c..ab5fcf6336 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap, this); - public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new TaikoLegacySkinTransformer(source); + public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TaikoLegacySkinTransformer(skin); public const string SHORT_NAME = "taiko"; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index cc53e50884..13e84e335d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -116,12 +116,12 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestOsuRuleset : OsuRuleset { - public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(source); + public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(skin); private class TestOsuLegacySkinTransformer : OsuLegacySkinTransformer { - public TestOsuLegacySkinTransformer(ISkinSource source) - : base(source) + public TestOsuLegacySkinTransformer(ISkin skin) + : base(skin) { } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 7bdf84ace4..9f9f42eda4 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets [CanBeNull] public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().FirstOrDefault(); - public virtual ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => null; + public virtual ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => null; protected Ruleset() { diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 651fdddb1b..cd896ab51e 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.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.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -16,16 +15,16 @@ namespace osu.Game.Skinning /// /// Transformer used to handle support of legacy features for individual rulesets. /// - public abstract class LegacySkinTransformer : ISkinSource + public abstract class LegacySkinTransformer : ISkin { /// - /// Source of the which is being transformed. + /// The which is being transformed. /// - protected ISkinSource Source { get; } + protected ISkin Skin { get; } - protected LegacySkinTransformer(ISkinSource source) + protected LegacySkinTransformer(ISkin skin) { - Source = source; + Skin = skin; } public abstract Drawable GetDrawableComponent(ISkinComponent component); @@ -33,28 +32,20 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName) => GetTexture(componentName, default, default); public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) - => Source.GetTexture(componentName, wrapModeS, wrapModeT); + => Skin.GetTexture(componentName, wrapModeS, wrapModeT); public virtual ISample GetSample(ISampleInfo sampleInfo) { if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) - return Source.GetSample(sampleInfo); + return Skin.GetSample(sampleInfo); var playLayeredHitSounds = GetConfig(LegacySetting.LayeredHitSounds); if (legacySample.IsLayered && playLayeredHitSounds?.Value == false) return new SampleVirtual(); - return Source.GetSample(sampleInfo); + return Skin.GetSample(sampleInfo); } public abstract IBindable GetConfig(TLookup lookup); - - public ISkin FindProvider(Func lookupFunction) => Source.FindProvider(lookupFunction); - - public event Action SourceChanged - { - add { throw new NotSupportedException(); } - remove { } - } } } From 6538d44708646a2b32b4aa0b112d7d31d82d38ff Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 9 Jun 2021 20:36:34 +0300 Subject: [PATCH 0589/1621] Make `SkinProvidingContainer` able to perform lookup on multiple skins Currently `protected` functionality for use in custom `SkinProvidingContainer`s, can be exposed to public constructors if it need to later on, but I'm not sure about doing that opposed to just nesting multiple `SkinProvidingContainer`. --- .../Gameplay/TestSceneSkinnableDrawable.cs | 2 +- .../Skinning/BeatmapSkinProvidingContainer.cs | 6 +- osu.Game/Skinning/SkinProvidingContainer.cs | 94 ++++++++++++------- 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 96418f6d28..77966e925a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void Disable() { allow = false; - TriggerSourceChanged(); + OnSourceChanged(); } public SwitchableSkinProvidingContainer(ISkin skin) diff --git a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs index 57c08a903f..f12f44e347 100644 --- a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs +++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs @@ -83,9 +83,9 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load() { - beatmapSkins.BindValueChanged(_ => TriggerSourceChanged()); - beatmapColours.BindValueChanged(_ => TriggerSourceChanged()); - beatmapHitsounds.BindValueChanged(_ => TriggerSourceChanged()); + beatmapSkins.BindValueChanged(_ => OnSourceChanged()); + beatmapColours.BindValueChanged(_ => OnSourceChanged()); + beatmapHitsounds.BindValueChanged(_ => OnSourceChanged()); } } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 0e16cf43ee..cc9d8d0e8d 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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -21,8 +22,10 @@ namespace osu.Game.Skinning { public event Action SourceChanged; - [CanBeNull] - private readonly ISkin skin; + /// + /// The list of skins provided by this . + /// + protected readonly List SkinLayers = new List(); [CanBeNull] private ISkinSource fallbackSource; @@ -38,23 +41,30 @@ namespace osu.Game.Skinning protected virtual bool AllowColourLookup => true; public SkinProvidingContainer(ISkin skin) + : this() { - this.skin = skin; + SkinLayers.Add(skin); + } + protected SkinProvidingContainer() + { RelativeSizeAxes = Axes.Both; } public ISkin FindProvider(Func lookupFunction) { - if (skin is ISkinSource source) + foreach (var skin in SkinLayers) { - if (source.FindProvider(lookupFunction) is ISkin found) - return found; - } - else if (skin != null) - { - if (lookupFunction(skin)) - return skin; + if (skin is ISkinSource source) + { + if (source.FindProvider(lookupFunction) is ISkin found) + return found; + } + else if (skin != null) + { + if (lookupFunction(skin)) + return skin; + } } return fallbackSource?.FindProvider(lookupFunction); @@ -62,57 +72,73 @@ namespace osu.Game.Skinning public Drawable GetDrawableComponent(ISkinComponent component) { - Drawable sourceDrawable; - if (AllowDrawableLookup(component) && (sourceDrawable = skin?.GetDrawableComponent(component)) != null) - return sourceDrawable; + if (AllowDrawableLookup(component)) + { + foreach (var skin in SkinLayers) + { + Drawable sourceDrawable; + if ((sourceDrawable = skin?.GetDrawableComponent(component)) != null) + return sourceDrawable; + } + } return fallbackSource?.GetDrawableComponent(component); } public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { - Texture sourceTexture; - if (AllowTextureLookup(componentName) && (sourceTexture = skin?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) - return sourceTexture; + if (AllowTextureLookup(componentName)) + { + foreach (var skin in SkinLayers) + { + Texture sourceTexture; + if ((sourceTexture = skin?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) + return sourceTexture; + } + } return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT); } public ISample GetSample(ISampleInfo sampleInfo) { - ISample sourceChannel; - if (AllowSampleLookup(sampleInfo) && (sourceChannel = skin?.GetSample(sampleInfo)) != null) - return sourceChannel; + if (AllowSampleLookup(sampleInfo)) + { + foreach (var skin in SkinLayers) + { + ISample sourceSample; + if ((sourceSample = skin?.GetSample(sampleInfo)) != null) + return sourceSample; + } + } return fallbackSource?.GetSample(sampleInfo); } public IBindable GetConfig(TLookup lookup) { - if (skin != null) - { - if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) - return lookupWithFallback(lookup, AllowColourLookup); + if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) + return lookupWithFallback(lookup, AllowColourLookup); - return lookupWithFallback(lookup, AllowConfigurationLookup); - } - - return fallbackSource?.GetConfig(lookup); + return lookupWithFallback(lookup, AllowConfigurationLookup); } private IBindable lookupWithFallback(TLookup lookup, bool canUseSkinLookup) { if (canUseSkinLookup) { - var bindable = skin?.GetConfig(lookup); - if (bindable != null) - return bindable; + foreach (var skin in SkinLayers) + { + IBindable bindable; + if ((bindable = skin?.GetConfig(lookup)) != null) + return bindable; + } } return fallbackSource?.GetConfig(lookup); } - protected virtual void TriggerSourceChanged() => SourceChanged?.Invoke(); + protected virtual void OnSourceChanged() => SourceChanged?.Invoke(); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -120,7 +146,7 @@ namespace osu.Game.Skinning fallbackSource = dependencies.Get(); if (fallbackSource != null) - fallbackSource.SourceChanged += TriggerSourceChanged; + fallbackSource.SourceChanged += OnSourceChanged; dependencies.CacheAs(this); @@ -135,7 +161,7 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); if (fallbackSource != null) - fallbackSource.SourceChanged -= TriggerSourceChanged; + fallbackSource.SourceChanged -= OnSourceChanged; } } } From 9e652715ced79d2a6f22180c03d3e6024a10da8d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 9 Jun 2021 20:40:47 +0300 Subject: [PATCH 0590/1621] Expose the skin lookup layers of `SkinManager` to a property --- osu.Game/Skinning/SkinManager.cs | 44 +++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 48d6b9254f..134156e44f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -42,6 +42,24 @@ namespace osu.Game.Skinning public readonly Bindable CurrentSkin = new Bindable(); public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; + /// + /// The skin layers of the currently selected user skin for performing lookups on, + /// in order of preference (user skin first, then fallback skins). + /// + public IEnumerable CurrentSkinLayers + { + get + { + yield return CurrentSkin.Value; + + // 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) + yield return defaultLegacySkin; + } + } + public override IEnumerable HandledExtensions => new[] { ".osk" }; protected override string[] HashableFileTypes => new[] { ".ini" }; @@ -220,11 +238,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; + foreach (var skin in CurrentSkinLayers) + { + if (lookupFunction(skin)) + return skin; + } return null; } @@ -232,16 +250,12 @@ namespace osu.Game.Skinning private T lookupWithFallback(Func func) where T : class { - var selectedSkin = func(CurrentSkin.Value); - - if (selectedSkin != null) - return selectedSkin; - - // 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) - return func(defaultLegacySkin); + foreach (var skin in CurrentSkinLayers) + { + var result = func(skin); + if (result != null) + return result; + } return null; } From 33a9cac398f446bb01b5e9e9bd9463f81edd62ae Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 9 Jun 2021 20:41:16 +0300 Subject: [PATCH 0591/1621] Add special `RulesetSkinProvidingContainer` managing ruleset-compatible skin setup --- .../Skinning/RulesetSkinProvidingContainer.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 osu.Game/Skinning/RulesetSkinProvidingContainer.cs diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs new file mode 100644 index 0000000000..dcf6281e38 --- /dev/null +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -0,0 +1,59 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; + +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. + /// + public class RulesetSkinProvidingContainer : SkinProvidingContainer + { + private readonly Ruleset ruleset; + private readonly IBeatmap beatmap; + + protected override Container Content { get; } + + public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, ISkin beatmapSkin) + { + this.ruleset = ruleset; + this.beatmap = beatmap; + + InternalChild = new BeatmapSkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkin, beatmap)) + { + Child = Content = new Container + { + RelativeSizeAxes = Axes.Both, + } + }; + } + + [Resolved] + private SkinManager skinManager { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + updateSkins(); + } + + protected override void OnSourceChanged() + { + updateSkins(); + base.OnSourceChanged(); + } + + private void updateSkins() + { + SkinLayers.Clear(); + SkinLayers.AddRange(skinManager.CurrentSkinLayers.Select(s => ruleset.CreateLegacySkinProvider(s, beatmap))); + } + } +} From e30f6581b384c35aa7dbe5e9d04ce254bb084e2d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 9 Jun 2021 21:30:26 +0300 Subject: [PATCH 0592/1621] Wrap gameplay content within a `RulesetSkinProvidingContainer` --- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 10 +--------- osu.Game/Screens/Play/Player.cs | 14 ++++---------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 61056aeced..b56f9bee14 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -73,15 +73,7 @@ namespace osu.Game.Screens.Edit.Compose { Debug.Assert(ruleset != null); - var beatmapSkinProvider = new BeatmapSkinProvidingContainer(beatmap.Value.Skin); - - // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation - // full access to all skin sources. - var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, EditorBeatmap.PlayableBeatmap)); - - // 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. - return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(content)); + return new RulesetSkinProvidingContainer(ruleset, EditorBeatmap.PlayableBeatmap, beatmap.Value.Skin).WithChild(content); } #region Input Handling diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f9036780aa..47c91cfc4d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -234,29 +234,23 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(GameplayBeatmap); - var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); - - // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation - // full access to all skin sources. - var rulesetSkinProvider = new SkinProvidingContainer(GameplayRuleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); + 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. - GameplayClockContainer.Add(beatmapSkinProvider.WithChild(rulesetSkinProvider)); + GameplayClockContainer.Add(rulesetSkinProvider); rulesetSkinProvider.AddRange(new[] { - // underlay and gameplay should have access the to skinning sources. + // underlay and gameplay should have access to the skinning sources. createUnderlayComponents(), createGameplayComponents(Beatmap.Value, playableBeatmap) }); // also give the HUD a ruleset container to allow rulesets to potentially override HUD elements (used to disable combo counters etc.) // we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there. - var hudRulesetContainer = new SkinProvidingContainer(GameplayRuleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); - // add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components. - GameplayClockContainer.Add(hudRulesetContainer.WithChild(createOverlayComponents(Beatmap.Value))); + rulesetSkinProvider.Add(createOverlayComponents(Beatmap.Value)); if (!DrawableRuleset.AllowGameplayOverlays) { From 1aaad7bfd442d74cfc28903fb4f4b35eb178edb7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 9 Jun 2021 22:24:53 +0300 Subject: [PATCH 0593/1621] Apply few adjustments to skinning overlays comment --- 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 47c91cfc4d..bec5181efe 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -247,9 +247,9 @@ namespace osu.Game.Screens.Play createGameplayComponents(Beatmap.Value, playableBeatmap) }); - // also give the HUD a ruleset container to allow rulesets to potentially override HUD elements (used to disable combo counters etc.) - // we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there. // add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components. + // also give the overlays the ruleset skin provider to allow rulesets to potentially override HUD elements (used to disable combo counters etc.) + // we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there. rulesetSkinProvider.Add(createOverlayComponents(Beatmap.Value)); if (!DrawableRuleset.AllowGameplayOverlays) From e3f3c379535d078d4c7e0003c555a20afe64b5b1 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 9 Jun 2021 17:03:46 -0700 Subject: [PATCH 0594/1621] Add ability to navigate score panels with left/right arrows --- osu.Game/Screens/Ranking/ScorePanelList.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 441c9e048a..e170241ede 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -8,9 +8,11 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Scoring; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Ranking { @@ -263,6 +265,26 @@ namespace osu.Game.Screens.Ranking container.Attach(); } + protected override bool OnKeyDown(KeyDownEvent e) + { + var expandedPanelIndex = flow.GetPanelIndex(expandedPanel.Score); + + switch (e.Key) + { + case Key.Left: + if (expandedPanelIndex > 0) + SelectedScore.Value = flow.Children[expandedPanelIndex - 1].Panel.Score; + return true; + + case Key.Right: + if (expandedPanelIndex < flow.Count - 1) + SelectedScore.Value = flow.Children[expandedPanelIndex + 1].Panel.Score; + return true; + } + + return base.OnKeyDown(e); + } + private class Flow : FillFlowContainer { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); From 39e1f77d537c0fc3df62dbde6b5bc9be13a1c9a5 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 10 Jun 2021 09:37:33 +0700 Subject: [PATCH 0595/1621] add image table with image content test --- .../Online/TestSceneWikiMarkdownContainer.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 9d8f07969c..b6dce2c398 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -143,6 +143,25 @@ Line after image"; }); } + [Test] + public void TestTableWithImageContent() + { + AddStep("Add Table", () => + { + markdownContainer.DocumentUrl = "https://dev.ppy.sh"; + markdownContainer.Text = @" +| Image | Name | Effect | +| :-: | :-: | :-- | +| ![](/wiki/Skinning/Interface/img/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. | +| ![](/wiki/Skinning/Interface/img/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. | +| ![](/wiki/Skinning/Interface/img/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. | +| ![](/wiki/Skinning/Interface/img/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. | +| ![](/wiki/Skinning/Interface/img/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. | +| ![](/wiki/Skinning/Interface/img/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. | +"; + }); + } + private class TestMarkdownContainer : WikiMarkdownContainer { public LinkInline Link; From 28d7b069085aa9721fc9d55b20963a34753606a1 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 10 Jun 2021 09:38:07 +0700 Subject: [PATCH 0596/1621] create OsuMarkdownImage --- .../Containers/Markdown/OsuMarkdownImage.cs | 20 +++++++++++++++++++ .../Markdown/OsuMarkdownTextFlowContainer.cs | 2 ++ 2 files changed, 22 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs new file mode 100644 index 0000000000..75c73af0ce --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.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 Markdig.Syntax.Inlines; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Cursor; + +namespace osu.Game.Graphics.Containers.Markdown +{ + public class OsuMarkdownImage : MarkdownImage, IHasTooltip + { + public string TooltipText { get; } + + public OsuMarkdownImage(LinkInline linkInline) + : base(linkInline.Url) + { + TooltipText = linkInline.Title; + } + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index f3308019ce..36b48b7769 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -17,6 +17,8 @@ namespace osu.Game.Graphics.Containers.Markdown protected override void AddLinkText(string text, LinkInline linkInline) => AddDrawable(new OsuMarkdownLinkText(text, linkInline)); + protected override void AddImage(LinkInline linkInline) => AddDrawable(new OsuMarkdownImage(linkInline)); + // TODO : Change font to monospace protected override void AddCodeInLine(CodeInline codeInline) => AddDrawable(new OsuMarkdownInlineCode { From 05cb935a940315ad6ff3bfdbed05c010ee09388c Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 10 Jun 2021 09:38:48 +0700 Subject: [PATCH 0597/1621] change WikiMarkdownImage to extend OsuMarkdownImage --- osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs index c2115efeb5..27d1fe9b2f 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs @@ -2,19 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using Markdig.Syntax.Inlines; -using osu.Framework.Graphics.Containers.Markdown; -using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics.Containers.Markdown; namespace osu.Game.Overlays.Wiki.Markdown { - public class WikiMarkdownImage : MarkdownImage, IHasTooltip + public class WikiMarkdownImage : OsuMarkdownImage { - public string TooltipText { get; } - public WikiMarkdownImage(LinkInline linkInline) - : base(linkInline.Url) + : base(linkInline) { - TooltipText = linkInline.Title; } protected override ImageContainer CreateImageContainer(string url) From 7815b3c72bed5856e52803d646097fdffc56e668 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 10 Jun 2021 10:58:42 +0800 Subject: [PATCH 0598/1621] Display results after fail --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 ++ osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 8 +++++++- osu.Game/Rulesets/Mods/IApplicableFailOverride.cs | 5 +++++ osu.Game/Rulesets/Mods/ModAutoplay.cs | 2 ++ osu.Game/Rulesets/Mods/ModBlockFail.cs | 1 + osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs | 1 + osu.Game/Rulesets/Mods/ModFailCondition.cs | 1 + osu.Game/Screens/Play/Player.cs | 10 ++++++++-- 8 files changed, 27 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index aac830801b..2b92174b29 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Osu.Mods public bool RestartOnFail => false; + public bool DisplayResultsOnFail => false; + private OsuInputManager inputManager; private IFrameStableClock gameplayClock; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 3fe0161742..c3e9e60420 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -36,7 +36,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModTarget : ModWithVisibilityAdjustment, IApplicableToDrawableRuleset, - IApplicableToHealthProcessor, IApplicableToDifficulty + IApplicableToHealthProcessor, IApplicableToDifficulty, IApplicableFailOverride { public override string Name => "Target"; public override string Acronym => "TP"; @@ -52,6 +52,12 @@ namespace osu.Game.Rulesets.Osu.Mods Value = null }; + public bool PerformFail() => true; + + public bool RestartOnFail => false; + + public bool DisplayResultsOnFail => true; + public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { // Sudden death diff --git a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs index 8c99d739cb..b7d306e57b 100644 --- a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs +++ b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs @@ -18,5 +18,10 @@ namespace osu.Game.Rulesets.Mods /// Whether we want to restart on fail. Only used if returns true. /// bool RestartOnFail { get; } + + /// + /// Whether to proceed to results screen on fail. Only used if returns true and is false. + /// + bool DisplayResultsOnFail { get; } } } diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index d6e1d46b06..3fb92527f2 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -35,6 +35,8 @@ namespace osu.Game.Rulesets.Mods public bool RestartOnFail => false; + public bool DisplayResultsOnFail => false; + public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) }; public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs index 1fde5abad4..973a192378 100644 --- a/osu.Game/Rulesets/Mods/ModBlockFail.cs +++ b/osu.Game/Rulesets/Mods/ModBlockFail.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Mods public bool PerformFail() => false; public bool RestartOnFail => false; + public bool DisplayResultsOnFail => false; public void ReadFromConfig(OsuConfigManager config) { diff --git a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs index 2ac0f59d84..91508b7e70 100644 --- a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs +++ b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs @@ -41,6 +41,7 @@ namespace osu.Game.Rulesets.Mods } public bool RestartOnFail => false; + public bool DisplayResultsOnFail => false; public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs index c0d7bae2b2..012a0f73ff 100644 --- a/osu.Game/Rulesets/Mods/ModFailCondition.cs +++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Mods public virtual bool PerformFail() => true; public virtual bool RestartOnFail => true; + public virtual bool DisplayResultsOnFail => false; public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 890883f0d2..b72524ad99 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -604,7 +604,7 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; - if (!ScoreProcessor.HasCompleted.Value) + if (!ScoreProcessor.HasCompleted.Value && !HealthProcessor.HasFailed) { completionProgressDelegate?.Cancel(); completionProgressDelegate = null; @@ -617,7 +617,7 @@ namespace osu.Game.Screens.Play throw new InvalidOperationException($"{nameof(updateCompletionState)} was fired more than once"); // Only show the completion screen if the player hasn't failed - if (HealthProcessor.HasFailed) + if (HealthProcessor.HasFailed && !Mods.Value.OfType().Any(m => m.DisplayResultsOnFail)) return; ValidForResume = false; @@ -712,6 +712,12 @@ namespace osu.Game.Screens.Play // Called back when the transform finishes private void onFailComplete() { + if (Mods.Value.OfType().Any(m => m.DisplayResultsOnFail)) + { + updateCompletionState(true); + return; + } + GameplayClockContainer.Stop(); FailOverlay.Retries = RestartCount; From 209d217024bdd1d37ab63e47f6d9a3dafb6e90b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Jun 2021 12:37:56 +0900 Subject: [PATCH 0599/1621] 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 cc38556f619c652d4b9bbc84d4948a059fe9aaf1 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 9 Jun 2021 23:26:57 -0700 Subject: [PATCH 0600/1621] Fix background being dimmed forever after toggling statistics in results screen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index c44ce63ccb..9f7d0935d5 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -318,7 +318,7 @@ namespace osu.Game.Screens.Ranking ScorePanelList.HandleInput = false; // Dim background. - ApplyToBackground(b => b.FadeTo(0.1f, 150)); + ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.1f), 150)); detachedPanel = expandedPanel; } @@ -342,7 +342,7 @@ namespace osu.Game.Screens.Ranking ScorePanelList.HandleInput = true; // Un-dim background. - ApplyToBackground(b => b.FadeTo(0.5f, 150)); + ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.5f), 150)); detachedPanel = null; } From 699594536048ec2f4519793d5184ad23ab9e0f27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Jun 2021 15:45:49 +0900 Subject: [PATCH 0601/1621] Use `With` to simplify drawable construction --- .../Skinning/Legacy/LegacyCatcherNew.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs index 5987e9e393..0a7d877627 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs @@ -35,18 +35,17 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy [BackgroundDependencyLoader] private void load(ISkinSource skin, Bindable currentState) { - CurrentState.BindTo(currentState); - foreach (var state in Enum.GetValues(typeof(CatcherAnimationState)).Cast()) { - var d = getDrawableFor(state); - d.Anchor = Anchor.TopCentre; - d.Origin = Anchor.TopCentre; - d.RelativeSizeAxes = Axes.Both; - d.Size = Vector2.One; - d.FillMode = FillMode.Fit; - d.Alpha = 0; - AddInternal(drawables[state] = d); + AddInternal(drawables[state] = getDrawableFor(state).With(d => + { + d.Anchor = Anchor.TopCentre; + d.Origin = Anchor.TopCentre; + d.RelativeSizeAxes = Axes.Both; + d.Size = Vector2.One; + d.FillMode = FillMode.Fit; + d.Alpha = 0; + })); } currentDrawable = drawables[CatcherAnimationState.Idle]; @@ -54,6 +53,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy Drawable getDrawableFor(CatcherAnimationState state) => skin.GetAnimation(@$"fruit-catcher-{state.ToString().ToLowerInvariant()}", true, true, true) ?? skin.GetAnimation(@"fruit-catcher-idle", true, true, true); + + CurrentState.BindTo(currentState); } protected override void LoadComplete() From 865c5c06766d76110aeb118dee47d74d1cb3637b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Jun 2021 15:47:03 +0900 Subject: [PATCH 0602/1621] Use `[Resolved]` to simplify bindable resolution --- .../Skinning/Legacy/LegacyCatcherNew.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs index 0a7d877627..2bf8b28aa2 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs @@ -19,7 +19,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy { public class LegacyCatcherNew : CompositeDrawable, ICatcherSprite { - public Bindable CurrentState { get; } = new Bindable(); + [Resolved] + private Bindable currentState { get; set; } public Texture CurrentTexture => (currentDrawable as TextureAnimation)?.CurrentFrame ?? (currentDrawable as Sprite)?.Texture; @@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(ISkinSource skin, Bindable currentState) + private void load(ISkinSource skin) { foreach (var state in Enum.GetValues(typeof(CatcherAnimationState)).Cast()) { @@ -53,15 +54,13 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy Drawable getDrawableFor(CatcherAnimationState state) => skin.GetAnimation(@$"fruit-catcher-{state.ToString().ToLowerInvariant()}", true, true, true) ?? skin.GetAnimation(@"fruit-catcher-idle", true, true, true); - - CurrentState.BindTo(currentState); } protected override void LoadComplete() { base.LoadComplete(); - CurrentState.BindValueChanged(state => + currentState.BindValueChanged(state => { currentDrawable.Alpha = 0; currentDrawable = drawables[state.NewValue]; From 12a17d0983c6f3557ef7c8231fca247f8311181f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 10 Jun 2021 15:31:20 +0800 Subject: [PATCH 0603/1621] Extract seed setting contorl to IHasSeed --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 90 +------------------- osu.Game/Rulesets/Mods/IHasSeed.cs | 95 ++++++++++++++++++++++ osu.Game/Rulesets/Mods/ModRandom.cs | 81 +----------------- 3 files changed, 99 insertions(+), 167 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IHasSeed.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index c3e9e60420..04a7365148 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -8,9 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -18,11 +16,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Multiplayer; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; @@ -36,7 +30,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModTarget : ModWithVisibilityAdjustment, IApplicableToDrawableRuleset, - IApplicableToHealthProcessor, IApplicableToDifficulty, IApplicableFailOverride + IApplicableToHealthProcessor, IApplicableToDifficulty, IApplicableFailOverride, IHasSeed { public override string Name => "Target"; public override string Acronym => "TP"; @@ -45,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Practice keeping up with the beat of the song."; public override double ScoreMultiplier => 1; - [SettingSource("Seed", "Seed for random circle placement", SettingControlType = typeof(OsuModTargetSettingsControl))] + [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SeedSettingsControl))] public Bindable Seed { get; } = new Bindable { Default = null, @@ -419,84 +413,4 @@ namespace osu.Game.Rulesets.Osu.Mods } } } - - public class OsuModTargetSettingsControl : SettingsItem - { - protected override Drawable CreateControl() => new SeedControl - { - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 5 } - }; - - private sealed class SeedControl : CompositeDrawable, IHasCurrentValue - { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current - { - get => current.Current; - set - { - current.Current = value; - seedNumberBox.Text = value.Value.ToString(); - } - } - - private readonly OsuNumberBox seedNumberBox; - - public SeedControl() - { - AutoSizeAxes = Axes.Y; - - InternalChildren = new[] - { - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 120) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - seedNumberBox = new OsuNumberBox - { - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true, - }, - new TriangleButton - { - Width = 120, - Text = "Randomise", - Action = () => current.Value = RNG.Next(), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - } - } - } - }; - - seedNumberBox.Current.BindValueChanged(e => - { - if (int.TryParse(e.NewValue, out var intVal)) - current.Value = intVal; - else - current.Value = null; - }); - current.BindValueChanged(e => - { - seedNumberBox.Text = e.NewValue.ToString(); - }); - } - } - } } diff --git a/osu.Game/Rulesets/Mods/IHasSeed.cs b/osu.Game/Rulesets/Mods/IHasSeed.cs new file mode 100644 index 0000000000..45a806e80c --- /dev/null +++ b/osu.Game/Rulesets/Mods/IHasSeed.cs @@ -0,0 +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.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Rulesets.Mods +{ + public interface IHasSeed + { + public Bindable Seed { get; } + } + + public class SeedSettingsControl : SettingsItem + { + protected override Drawable CreateControl() => new SeedControl + { + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 5 } + }; + + private sealed class SeedControl : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current; + set + { + current.Current = value; + seedNumberBox.Text = value.Value.ToString(); + } + } + + private readonly OsuNumberBox seedNumberBox; + + public SeedControl() + { + AutoSizeAxes = Axes.Y; + + InternalChildren = new[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(GridSizeMode.Relative, 0.25f) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + seedNumberBox = new OsuNumberBox + { + RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true + } + } + } + } + }; + + seedNumberBox.Current.BindValueChanged(e => + { + int? value = null; + + if (int.TryParse(e.NewValue, out var intVal)) + value = intVal; + + current.Value = value; + }); + } + + protected override void Update() + { + if (current.Value == null) + seedNumberBox.Text = current.Current.Value.ToString(); + } + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index 3f14263420..eec66161ff 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -13,7 +13,7 @@ using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods { - public abstract class ModRandom : Mod + public abstract class ModRandom : Mod, IHasSeed { public override string Name => "Random"; public override string Acronym => "RD"; @@ -21,88 +21,11 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.Dice; public override double ScoreMultiplier => 1; - [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(ModRandomSettingsControl))] + [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SeedSettingsControl))] public Bindable Seed { get; } = new Bindable { Default = null, Value = null }; - - private class ModRandomSettingsControl : SettingsItem - { - protected override Drawable CreateControl() => new SeedControl - { - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 5 } - }; - - private sealed class SeedControl : CompositeDrawable, IHasCurrentValue - { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current - { - get => current; - set - { - current.Current = value; - seedNumberBox.Text = value.Value.ToString(); - } - } - - private readonly OsuNumberBox seedNumberBox; - - public SeedControl() - { - AutoSizeAxes = Axes.Y; - - InternalChildren = new[] - { - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), - new Dimension(GridSizeMode.Relative, 0.25f) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - seedNumberBox = new OsuNumberBox - { - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true - } - } - } - } - }; - - seedNumberBox.Current.BindValueChanged(e => - { - int? value = null; - - if (int.TryParse(e.NewValue, out var intVal)) - value = intVal; - - current.Value = value; - }); - } - - protected override void Update() - { - if (current.Value == null) - seedNumberBox.Text = current.Current.Value.ToString(); - } - } - } } } From 4f8000a5744703d8235df2054ac4da7ed4d8a531 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Jun 2021 17:29:36 +0900 Subject: [PATCH 0604/1621] 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 0605/1621] 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 0606/1621] 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 18edbdd135dc31e989fe8f801b612a267ee6e415 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 11:55:22 +0300 Subject: [PATCH 0607/1621] Remove mentioning of "layer" in skin providers `SkinSources` sounds better. --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 4 ++-- osu.Game/Skinning/SkinProvidingContainer.cs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index dcf6281e38..1fe86d2873 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -52,8 +52,8 @@ namespace osu.Game.Skinning private void updateSkins() { - SkinLayers.Clear(); - SkinLayers.AddRange(skinManager.CurrentSkinLayers.Select(s => ruleset.CreateLegacySkinProvider(s, beatmap))); + SkinSources.Clear(); + SkinSources.AddRange(skinManager.CurrentSkinLayers.Select(s => ruleset.CreateLegacySkinProvider(s, beatmap))); } } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index cc9d8d0e8d..6686583a6f 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Skinning /// /// The list of skins provided by this . /// - protected readonly List SkinLayers = new List(); + protected readonly List SkinSources = new List(); [CanBeNull] private ISkinSource fallbackSource; @@ -43,7 +43,7 @@ namespace osu.Game.Skinning public SkinProvidingContainer(ISkin skin) : this() { - SkinLayers.Add(skin); + SkinSources.Add(skin); } protected SkinProvidingContainer() @@ -53,7 +53,7 @@ namespace osu.Game.Skinning public ISkin FindProvider(Func lookupFunction) { - foreach (var skin in SkinLayers) + foreach (var skin in SkinSources) { if (skin is ISkinSource source) { @@ -74,7 +74,7 @@ namespace osu.Game.Skinning { if (AllowDrawableLookup(component)) { - foreach (var skin in SkinLayers) + foreach (var skin in SkinSources) { Drawable sourceDrawable; if ((sourceDrawable = skin?.GetDrawableComponent(component)) != null) @@ -89,7 +89,7 @@ namespace osu.Game.Skinning { if (AllowTextureLookup(componentName)) { - foreach (var skin in SkinLayers) + foreach (var skin in SkinSources) { Texture sourceTexture; if ((sourceTexture = skin?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) @@ -104,7 +104,7 @@ namespace osu.Game.Skinning { if (AllowSampleLookup(sampleInfo)) { - foreach (var skin in SkinLayers) + foreach (var skin in SkinSources) { ISample sourceSample; if ((sourceSample = skin?.GetSample(sampleInfo)) != null) @@ -127,7 +127,7 @@ namespace osu.Game.Skinning { if (canUseSkinLookup) { - foreach (var skin in SkinLayers) + foreach (var skin in SkinSources) { IBindable bindable; if ((bindable = skin?.GetConfig(lookup)) != null) From 530026b6755c6e5891969dd1d0c594da67c3ffd2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 11:56:13 +0300 Subject: [PATCH 0608/1621] Add simple xmldoc to ctors explaining their deal with `SkinSources` --- osu.Game/Skinning/SkinProvidingContainer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 6686583a6f..a7bc3ba379 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -40,12 +40,19 @@ namespace osu.Game.Skinning protected virtual bool AllowColourLookup => true; + /// + /// Constructs a new with a single skin added to the protected list. + /// public SkinProvidingContainer(ISkin skin) : this() { SkinSources.Add(skin); } + /// + /// Constructs a new with no sources. + /// Up to the implementation for adding to the list. + /// protected SkinProvidingContainer() { RelativeSizeAxes = Axes.Both; From 58cca9da06ecd1081b33f03ea57ea9cea8e88c0c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 11:57:28 +0300 Subject: [PATCH 0609/1621] Revert "Expose the skin lookup layers of `SkinManager` to a property" This reverts commit 9e652715ced79d2a6f22180c03d3e6024a10da8d. --- osu.Game/Skinning/SkinManager.cs | 44 +++++++++++--------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 134156e44f..48d6b9254f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -42,24 +42,6 @@ namespace osu.Game.Skinning public readonly Bindable CurrentSkin = new Bindable(); public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; - /// - /// The skin layers of the currently selected user skin for performing lookups on, - /// in order of preference (user skin first, then fallback skins). - /// - public IEnumerable CurrentSkinLayers - { - get - { - yield return CurrentSkin.Value; - - // 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) - yield return defaultLegacySkin; - } - } - public override IEnumerable HandledExtensions => new[] { ".osk" }; protected override string[] HashableFileTypes => new[] { ".ini" }; @@ -238,11 +220,11 @@ namespace osu.Game.Skinning public ISkin FindProvider(Func lookupFunction) { - foreach (var skin in CurrentSkinLayers) - { - if (lookupFunction(skin)) - return skin; - } + if (lookupFunction(CurrentSkin.Value)) + return CurrentSkin.Value; + + if (CurrentSkin.Value is LegacySkin && lookupFunction(defaultLegacySkin)) + return defaultLegacySkin; return null; } @@ -250,12 +232,16 @@ namespace osu.Game.Skinning private T lookupWithFallback(Func func) where T : class { - foreach (var skin in CurrentSkinLayers) - { - var result = func(skin); - if (result != null) - return result; - } + var selectedSkin = func(CurrentSkin.Value); + + if (selectedSkin != null) + return selectedSkin; + + // 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) + return func(defaultLegacySkin); return null; } From 59be3588eb281d916ba68297f19e6eb4a5c362ed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 12:17:51 +0300 Subject: [PATCH 0610/1621] Change `SkinSources` to a bindable list for binding `SourceChanged` events --- osu.Game/Skinning/SkinProvidingContainer.cs | 44 +++++++++++---------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 3739172367..fa0780ff29 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -2,7 +2,8 @@ // 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; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -25,7 +26,7 @@ namespace osu.Game.Skinning /// /// The list of skins provided by this . /// - protected readonly List SkinSources = new List(); + protected readonly BindableList SkinSources = new BindableList(); [CanBeNull] private ISkinSource fallbackSource; @@ -61,8 +62,23 @@ namespace osu.Game.Skinning noFallbackLookupProxy = new NoFallbackProxy(this); - if (skin is ISkinSource source) - source.SourceChanged += TriggerSourceChanged; + SkinSources.BindCollectionChanged(((_, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Remove: + foreach (var source in args.OldItems.Cast().OfType()) + source.SourceChanged -= OnSourceChanged; + + break; + + case NotifyCollectionChangedAction.Add: + foreach (var source in args.NewItems.Cast().OfType()) + source.SourceChanged += OnSourceChanged; + + break; + } + }), true); } public ISkin FindProvider(Func lookupFunction) @@ -146,21 +162,9 @@ namespace osu.Game.Skinning public IBindable GetConfig(TLookup lookup, bool fallback) { if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) - return lookupWithFallback(lookup, AllowColourLookup); + return lookupWithFallback(lookup, AllowColourLookup, fallback); - return lookupWithFallback(lookup, AllowConfigurationLookup); - if (skin != null) - { - if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) - return lookupWithFallback(lookup, AllowColourLookup, fallback); - - return lookupWithFallback(lookup, AllowConfigurationLookup, fallback); - } - - if (!fallback) - return null; - - return fallbackSource?.GetConfig(lookup); + return lookupWithFallback(lookup, AllowConfigurationLookup, fallback); } private IBindable lookupWithFallback(TLookup lookup, bool canUseSkinLookup, bool canUseFallback) @@ -205,10 +209,8 @@ namespace osu.Game.Skinning if (fallbackSource != null) fallbackSource.SourceChanged -= OnSourceChanged; - fallbackSource.SourceChanged -= TriggerSourceChanged; - if (skin is ISkinSource source) - source.SourceChanged -= TriggerSourceChanged; + SkinSources.Clear(); } private class NoFallbackProxy : ISkinSource From c3a2f2c2a4559b3740368065b78564c1306dd3cf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 13:04:34 +0300 Subject: [PATCH 0611/1621] Expose default `SkinManager` providers for use in `RulesetSkinProvidingContainer` --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 9 +++++++-- osu.Game/Skinning/SkinManager.cs | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 1fe86d2873..eadf0e05b9 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.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.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -53,7 +52,13 @@ namespace osu.Game.Skinning private void updateSkins() { SkinSources.Clear(); - SkinSources.AddRange(skinManager.CurrentSkinLayers.Select(s => ruleset.CreateLegacySkinProvider(s, beatmap))); + + SkinSources.Add(ruleset.CreateLegacySkinProvider(skinManager.CurrentSkin.Value, beatmap)); + + if (skinManager.CurrentSkin.Value is LegacySkin) + SkinSources.Add(ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, beatmap)); + + SkinSources.Add(ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, beatmap)); } } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 9e274227a2..89f166dc2a 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -48,9 +48,19 @@ 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; - private readonly Skin defaultSkin; + /// + /// An providing the resources of the default legacy skin. + /// + public ISkin DefaultLegacySkin => defaultLegacySkin; public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, IResourceStore resources, AudioManager audio) : base(storage, contextFactory, new SkinStore(contextFactory, storage), host) @@ -84,7 +94,7 @@ namespace osu.Game.Skinning { var userSkins = GetAllUserSkins(); userSkins.Insert(0, SkinInfo.Default); - userSkins.Insert(1, DefaultLegacySkin.Info); + userSkins.Insert(1, Skinning.DefaultLegacySkin.Info); return userSkins; } From 26cdcc8d78f4bb29d2730c7ad291db0651014177 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 13:05:44 +0300 Subject: [PATCH 0612/1621] Remove stale access to `Source` from master merge --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index a196ebedd7..a5a1d1504f 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy return null; case CatchSkinComponents.Catcher: - var version = Source.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value ?? 1; + var version = GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value ?? 1; if (version < 2.3m) { From 5c9c424a0d032ec412d7be58953176d86886b233 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 13:15:18 +0300 Subject: [PATCH 0613/1621] Switch state case placements for consistency Tickled me. --- osu.Game/Skinning/SkinProvidingContainer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index fa0780ff29..74a465a91f 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -66,17 +66,17 @@ namespace osu.Game.Skinning { switch (args.Action) { - case NotifyCollectionChangedAction.Remove: - foreach (var source in args.OldItems.Cast().OfType()) - source.SourceChanged -= OnSourceChanged; - - break; - case NotifyCollectionChangedAction.Add: foreach (var source in args.NewItems.Cast().OfType()) source.SourceChanged += OnSourceChanged; break; + + case NotifyCollectionChangedAction.Remove: + foreach (var source in args.OldItems.Cast().OfType()) + source.SourceChanged -= OnSourceChanged; + + break; } }), true); } 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 0614/1621] 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 0615/1621] 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 9eecb23fefa623356228bc916dba930e784dc035 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Jun 2021 21:35:52 +0900 Subject: [PATCH 0616/1621] Only seek to the end on first frame arrival --- osu.Game/Screens/Play/SpectatorPlayer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 0286b6b8a6..304bc4d7ed 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -48,7 +48,6 @@ namespace osu.Game.Screens.Play base.StartGameplay(); spectatorClient.OnNewFrames += userSentFrames; - seekToGameplay(); } private void userSentFrames(int userId, FrameDataBundle bundle) From 5073ede115f1c3da87e4c8055b8b39bbde31bdd8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Jun 2021 21:38:20 +0900 Subject: [PATCH 0617/1621] Clear existing frames when starting gameplay --- osu.Game/Screens/Play/SpectatorPlayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 304bc4d7ed..4d3b5eefb1 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -47,6 +47,8 @@ namespace osu.Game.Screens.Play { base.StartGameplay(); + // Start gameplay along with the very first arrival frame (the latest one). + score.Replay.Frames.Clear(); spectatorClient.OnNewFrames += userSentFrames; } From e18f4cd4bae79bd52801857a6e4deeefff3f35d5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Jun 2021 21:39:13 +0900 Subject: [PATCH 0618/1621] Combine methods --- osu.Game/Screens/Play/SpectatorPlayer.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 4d3b5eefb1..f662a479ec 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -63,6 +63,8 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; + bool isFirstBundle = score.Replay.Frames.Count == 0; + foreach (var frame in bundle.Frames) { IConvertibleReplayFrame convertibleFrame = GameplayRuleset.CreateConvertibleReplayFrame(); @@ -74,19 +76,8 @@ namespace osu.Game.Screens.Play score.Replay.Frames.Add(convertedFrame); } - seekToGameplay(); - } - - private bool seekedToGameplay; - - private void seekToGameplay() - { - if (seekedToGameplay || score.Replay.Frames.Count == 0) - return; - - NonFrameStableSeek(score.Replay.Frames[0].Time); - - seekedToGameplay = true; + if (isFirstBundle && score.Replay.Frames.Count > 0) + NonFrameStableSeek(score.Replay.Frames[0].Time); } protected override Score CreateScore() => score; From 09a2d008d226c8d0448067ce6b37d8d4cce86f87 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 13:41:41 +0300 Subject: [PATCH 0619/1621] Refrain from attempting to transform null skins --- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Skinning/LegacySkinTransformer.cs | 7 +++++-- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 5 +++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 9f9f42eda4..9fdaca88fd 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets [CanBeNull] public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().FirstOrDefault(); - public virtual ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => null; + public virtual ISkin CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null; protected Ruleset() { diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index cd896ab51e..fedd63c7de 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -1,6 +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 JetBrains.Annotations; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -20,11 +22,12 @@ namespace osu.Game.Skinning /// /// The which is being transformed. /// + [NotNull] protected ISkin Skin { get; } - protected LegacySkinTransformer(ISkin skin) + protected LegacySkinTransformer([NotNull] ISkin skin) { - Skin = skin; + Skin = skin ?? throw new ArgumentNullException(nameof(skin)); } public abstract Drawable GetDrawableComponent(ISkinComponent component); diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index eadf0e05b9..18399cbdb4 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,12 +21,12 @@ namespace osu.Game.Skinning protected override Container Content { get; } - public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, ISkin beatmapSkin) + public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) { this.ruleset = ruleset; this.beatmap = beatmap; - InternalChild = new BeatmapSkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkin, beatmap)) + InternalChild = new BeatmapSkinProvidingContainer(beatmapSkin == null ? null : ruleset.CreateLegacySkinProvider(beatmapSkin, beatmap)) { Child = Content = new Container { From ef2c4fd0d8ec68e8754aa12cf5cf381a5f839ed9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 15:05:08 +0300 Subject: [PATCH 0620/1621] Make `RulesetSkinProvidingContainer` able to be overriden for testing purposes --- osu.Game/Screens/Play/Player.cs | 4 +++- .../Skinning/RulesetSkinProvidingContainer.cs | 20 +++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index bec5181efe..fbcc7ea96f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -234,7 +234,7 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(GameplayBeatmap); - var rulesetSkinProvider = new RulesetSkinProvidingContainer(GameplayRuleset, playableBeatmap, Beatmap.Value.Skin); + var rulesetSkinProvider = CreateRulesetSkinProvider(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. @@ -315,6 +315,8 @@ 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/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 18399cbdb4..8087043230 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -16,15 +16,15 @@ namespace osu.Game.Skinning ///
public class RulesetSkinProvidingContainer : SkinProvidingContainer { - private readonly Ruleset ruleset; - private readonly IBeatmap beatmap; + protected readonly Ruleset Ruleset; + protected readonly IBeatmap Beatmap; protected override Container Content { get; } public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) { - this.ruleset = ruleset; - this.beatmap = beatmap; + Ruleset = ruleset; + Beatmap = beatmap; InternalChild = new BeatmapSkinProvidingContainer(beatmapSkin == null ? null : ruleset.CreateLegacySkinProvider(beatmapSkin, beatmap)) { @@ -41,25 +41,25 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load() { - updateSkins(); + UpdateSkins(); } protected override void OnSourceChanged() { - updateSkins(); + UpdateSkins(); base.OnSourceChanged(); } - private void updateSkins() + protected virtual void UpdateSkins() { SkinSources.Clear(); - SkinSources.Add(ruleset.CreateLegacySkinProvider(skinManager.CurrentSkin.Value, beatmap)); + SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.CurrentSkin.Value, Beatmap)); if (skinManager.CurrentSkin.Value is LegacySkin) - SkinSources.Add(ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, beatmap)); + SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, Beatmap)); - SkinSources.Add(ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, beatmap)); + SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, Beatmap)); } } } From 23d6c366acc34ee9f92a116ce2cace40008f3b04 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 15:36:53 +0300 Subject: [PATCH 0621/1621] Add method for assigning arbitrary skins to player in test scenes --- osu.Game/Tests/Visual/PlayerTestScene.cs | 8 +++++++ osu.Game/Tests/Visual/TestPlayer.cs | 28 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 088e997de9..e42a043eec 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -10,6 +10,7 @@ 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 { @@ -78,6 +79,8 @@ namespace osu.Game.Tests.Visual } Player = CreatePlayer(ruleset); + Player.Skin = GetPlayerSkin(); + LoadScreen(Player); } @@ -93,6 +96,11 @@ 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 09da4db952..eecf8a2f6e 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -3,13 +3,17 @@ using System.Collections.Generic; 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 { @@ -18,6 +22,8 @@ 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; @@ -74,5 +80,27 @@ namespace osu.Game.Tests.Visual { ScoreProcessor.NewJudgement += r => Results.Add(r); } + + protected override RulesetSkinProvidingContainer CreateRulesetSkinProvider(Ruleset ruleset, IBeatmap beatmap, ISkin beatmapSkin) + => 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, Ruleset.CreateLegacySkinProvider(skin, Beatmap)); + } + } } } From 680791301f70b8476bb40af982e6adba8254bcb5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 16:36:27 +0300 Subject: [PATCH 0622/1621] Consume new method rather than caching skin sources on top of `Player` --- .../TestSceneLegacyBeatmapSkin.cs | 7 +--- .../TestSceneSkinFallbacks.cs | 26 +----------- .../Tests/Beatmaps/HitObjectSampleTest.cs | 42 ++----------------- .../Beatmaps/LegacyBeatmapSkinColourTest.cs | 32 ++++---------- .../Tests/Visual/LegacySkinPlayerTestScene.cs | 17 +------- 5 files changed, 16 insertions(+), 108 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs index bc3daca16f..0077ff9e3c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs @@ -101,15 +101,10 @@ 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(bool userHasCustomColours) => new CatchExposedPlayer(userHasCustomColours); + protected override ExposedPlayer CreateTestPlayer() => new CatchExposedPlayer(); 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 fd523fffcb..2b45818aa9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -21,7 +21,6 @@ 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 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Resolved] private AudioManager audio { get; set; } - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); + protected override ISkin GetPlayerSkin() => testUserSkin; protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, audio, testBeatmapSkin); @@ -116,27 +115,6 @@ 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; @@ -164,8 +142,8 @@ namespace osu.Game.Rulesets.Osu.Tests public ISample GetSample(ISampleInfo sampleInfo) => null; - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default; public IBindable GetConfig(TLookup lookup) => null; + public ISkin FindProvider(Func lookupFunction) => null; public event Action SourceChanged; diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 7ee6c519b7..7af0397726 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -47,15 +47,11 @@ 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) @@ -63,6 +59,8 @@ 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(() => @@ -109,8 +107,7 @@ namespace osu.Game.Tests.Beatmaps } }; - // Need to refresh the cached skin source to refresh the skin resource store. - dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, this)); + Skin = new LegacySkin(userSkinInfo, this); }); } @@ -132,39 +129,6 @@ 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 2540b6d7da..1feb3eebbf 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -4,13 +4,11 @@ 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; @@ -49,36 +47,24 @@ namespace osu.Game.Tests.Beatmaps protected virtual ExposedPlayer LoadBeatmap(bool userHasCustomColours) { - ExposedPlayer player; - Beatmap.Value = testBeatmap; - LoadScreen(player = CreateTestPlayer(userHasCustomColours)); + ExposedPlayer player = CreateTestPlayer(); + + player.Skin = new TestSkin(userHasCustomColours); + + LoadScreen(player); return player; } - protected virtual ExposedPlayer CreateTestPlayer(bool userHasCustomColours) => new ExposedPlayer(userHasCustomColours); + protected virtual ExposedPlayer CreateTestPlayer() => new ExposedPlayer(); - protected class ExposedPlayer : Player + protected class ExposedPlayer : TestPlayer { - protected readonly bool UserHasCustomColours; - - public ExposedPlayer(bool userHasCustomColours) - : base(new PlayerConfiguration - { - AllowPause = false, - ShowResults = false, - }) + public ExposedPlayer() + : base(false, 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 b810bbf6ae..14a928d3c1 100644 --- a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs +++ b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs @@ -5,7 +5,6 @@ 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,15 +14,12 @@ namespace osu.Game.Tests.Visual { protected LegacySkin LegacySkin { get; private set; } - private ISkinSource legacySkinSource; - - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(legacySkinSource); + protected override ISkin GetPlayerSkin() => LegacySkin; [BackgroundDependencyLoader] private void load(SkinManager skins) { LegacySkin = new DefaultLegacySkin(skins); - legacySkinSource = new SkinProvidingContainer(LegacySkin); } [SetUpSteps] @@ -48,16 +44,5 @@ namespace osu.Game.Tests.Visual t.Reload(); })); } - - public class SkinProvidingPlayer : TestPlayer - { - [Cached(typeof(ISkinSource))] - private readonly ISkinSource skinSource; - - public SkinProvidingPlayer(ISkinSource skinSource) - { - this.skinSource = skinSource; - } - } } } From 58d71e4aead45fd8a756d574d904ded7ee5e3e3e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Jun 2021 22:41:38 +0900 Subject: [PATCH 0623/1621] Remove local "next frame" storage --- .../Visual/Gameplay/TestSceneSpectator.cs | 17 +++---- .../TestSceneMultiSpectatorLeaderboard.cs | 4 +- .../TestSceneMultiSpectatorScreen.cs | 12 +---- .../Visual/Spectator/TestSpectatorClient.cs | 44 ++++++++++++++----- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 6eeb3596a8..0926b8778b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -41,8 +41,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private OsuGameBase game { get; set; } - private int nextFrame; - private BeatmapSetInfo importedBeatmap; private int importedBeatmapId; @@ -51,8 +49,6 @@ namespace osu.Game.Tests.Visual.Gameplay { base.SetUpSteps(); - AddStep("reset sent frames", () => nextFrame = 0); - AddStep("import beatmap", () => { importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; @@ -105,7 +101,8 @@ namespace osu.Game.Tests.Visual.Gameplay waitForPlayer(); checkPaused(true); - sendFrames(1000); // send enough frames to ensure play won't be paused + // send enough frames to ensure play won't be paused + sendFrames(100); checkPaused(false); } @@ -114,12 +111,12 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestSpectatingDuringGameplay() { start(); + sendFrames(300); loadSpectatingScreen(); waitForPlayer(); - AddStep("advance frame count", () => nextFrame = 300); - sendFrames(); + sendFrames(300); AddUntilStep("playing from correct point in time", () => player.ChildrenOfType().First().FrameStableClock.CurrentTime > 30000); } @@ -220,11 +217,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void sendFrames(int count = 10) { - AddStep("send frames", () => - { - testSpectatorClient.SendFrames(streamingUser.Id, nextFrame, count); - nextFrame += count; - }); + AddStep("send frames", () => testSpectatorClient.SendFrames(streamingUser.Id, count)); } private void loadSpectatingScreen() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 5ad35be0ec..7f8a44fb98 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -96,10 +96,10 @@ namespace osu.Game.Tests.Visual.Multiplayer // For player 2, send frames in sets of 10. for (int i = 0; i < 100; i++) { - spectatorClient.SendFrames(PLAYER_1_ID, i, 1); + spectatorClient.SendFrames(PLAYER_1_ID, 1); if (i % 10 == 0) - spectatorClient.SendFrames(PLAYER_2_ID, i, 10); + spectatorClient.SendFrames(PLAYER_2_ID, 10); } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b91391c409..f5032fc2a5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -37,7 +37,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private MultiSpectatorScreen spectatorScreen; private readonly List playingUserIds = new List(); - private readonly Dictionary nextFrame = new Dictionary(); private BeatmapSetInfo importedSet; private BeatmapInfo importedBeatmap; @@ -55,8 +54,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.SetUpSteps(); - AddStep("reset sent frames", () => nextFrame.Clear()); - AddStep("add streaming client", () => { Remove(spectatorClient); @@ -80,8 +77,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Client.CurrentMatchPlayingUserIds.Add(PLAYER_2_ID); playingUserIds.Add(PLAYER_1_ID); playingUserIds.Add(PLAYER_2_ID); - nextFrame[PLAYER_1_ID] = 0; - nextFrame[PLAYER_2_ID] = 0; }); loadSpectateScreen(false); @@ -253,7 +248,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Client.CurrentMatchPlayingUserIds.Add(id); spectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); playingUserIds.Add(id); - nextFrame[id] = 0; } }); } @@ -264,7 +258,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { spectatorClient.EndPlay(userId); playingUserIds.Remove(userId); - nextFrame.Remove(userId); }); } @@ -275,10 +268,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("send frames", () => { foreach (int id in userIds) - { - spectatorClient.SendFrames(id, nextFrame[id], count); - nextFrame[id] += count; - } + spectatorClient.SendFrames(id, count); }); } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index c7aa43b377..f206d4f8b0 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -20,9 +20,15 @@ namespace osu.Game.Tests.Visual.Spectator { public class TestSpectatorClient : SpectatorClient { + /// + /// Maximum number of frames sent per bundle via . + /// + public const int FRAME_BUNDLE_SIZE = 10; + public override IBindable IsConnected { get; } = new Bindable(true); private readonly Dictionary userBeatmapDictionary = new Dictionary(); + private readonly Dictionary userNextFrameDictionary = new Dictionary(); [Resolved] private IAPIProvider api { get; set; } = null!; @@ -35,6 +41,7 @@ namespace osu.Game.Tests.Visual.Spectator public void StartPlay(int userId, int beatmapId) { userBeatmapDictionary[userId] = beatmapId; + userNextFrameDictionary[userId] = 0; sendPlayingState(userId); } @@ -57,24 +64,41 @@ namespace osu.Game.Tests.Visual.Spectator public new void Schedule(Action action) => base.Schedule(action); /// - /// Sends frames for an arbitrary user. + /// Sends frames for an arbitrary user, in bundles containing 10 frames each. /// /// The user to send frames for. - /// The frame index. - /// The number of frames to send. - public void SendFrames(int userId, int index, int count) + /// The total number of frames to send. + public void SendFrames(int userId, int count) { var frames = new List(); - for (int i = index; i < index + count; i++) - { - var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1; + int currentFrameIndex = userNextFrameDictionary[userId]; + int lastFrameIndex = currentFrameIndex + count - 1; - frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); + for (; currentFrameIndex <= lastFrameIndex; currentFrameIndex++) + { + // This is done in the next frame so that currentFrameIndex is updated to the correct value. + if (frames.Count == FRAME_BUNDLE_SIZE) + flush(); + + var buttonState = currentFrameIndex == lastFrameIndex ? ReplayButtonState.None : ReplayButtonState.Left1; + frames.Add(new LegacyReplayFrame(currentFrameIndex * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); } - var bundle = new FrameDataBundle(new ScoreInfo { Combo = index + count }, frames); - ((ISpectatorClient)this).UserSentFrames(userId, bundle); + flush(); + + userNextFrameDictionary[userId] = currentFrameIndex; + + void flush() + { + if (frames.Count == 0) + return; + + var bundle = new FrameDataBundle(new ScoreInfo { Combo = currentFrameIndex }, frames.ToArray()); + ((ISpectatorClient)this).UserSentFrames(userId, bundle); + + frames.Clear(); + } } protected override Task BeginPlayingInternal(SpectatorState state) From 2240e2c39c7b1bf3ebe846b3d8c40119086b0e58 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 17:23:15 +0300 Subject: [PATCH 0624/1621] Refrain from attempting to clear skin sources in disposal `Drawable.Dispose` is usually in an asynchronous context (async disposals stuff) and therefore this could cause a "collection was modified; enumeration opeartion may not execute" exception. --- osu.Game/Skinning/SkinProvidingContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 74a465a91f..078c666472 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -210,7 +210,8 @@ namespace osu.Game.Skinning if (fallbackSource != null) fallbackSource.SourceChanged -= OnSourceChanged; - SkinSources.Clear(); + foreach (var source in SkinSources.OfType()) + source.SourceChanged -= OnSourceChanged; } private class NoFallbackProxy : ISkinSource 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 0625/1621] 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 0626/1621] 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 0627/1621] 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 0628/1621] 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 169c98f963e3703e6d2166081b43ab3ac3dec66a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 00:25:19 +0300 Subject: [PATCH 0629/1621] Add skin providing ruleset resources in `RulesetSkinProvidingContainer` --- osu.Game/Skinning/RulesetResourcesSkin.cs | 51 +++++++++++++++++++ .../Skinning/RulesetSkinProvidingContainer.cs | 10 ++++ 2 files changed, 61 insertions(+) create mode 100644 osu.Game/Skinning/RulesetResourcesSkin.cs diff --git a/osu.Game/Skinning/RulesetResourcesSkin.cs b/osu.Game/Skinning/RulesetResourcesSkin.cs new file mode 100644 index 0000000000..1905a8a899 --- /dev/null +++ b/osu.Game/Skinning/RulesetResourcesSkin.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; +using osu.Framework.Platform; +using osu.Game.Audio; +using osu.Game.Rulesets; + +namespace osu.Game.Skinning +{ + /// + /// An providing the resources of the ruleset for accessibility during lookups. + /// + public class RulesetResourcesSkin : ISkin + { + private readonly TextureStore rulesetTextures; + private readonly ISampleStore rulesetSamples; + + public RulesetResourcesSkin(Ruleset ruleset, GameHost host, AudioManager audio) + { + IResourceStore rulesetResources = ruleset.CreateResourceStore(); + + rulesetTextures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(rulesetResources, @"Textures"))); + rulesetSamples = audio.GetSampleStore(new NamespacedResourceStore(rulesetResources, @"Samples")); + } + + public Drawable GetDrawableComponent(ISkinComponent component) => null; + + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => rulesetTextures.Get(componentName, wrapModeS, wrapModeT); + + public ISample GetSample(ISampleInfo sampleInfo) + { + foreach (var lookup in sampleInfo.LookupNames) + { + ISample sample = rulesetSamples.Get(lookup); + if (sample != null) + return sample; + } + + return null; + } + + public IBindable GetConfig(TLookup lookup) => null; + } +} diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8087043230..3d11b58a01 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -3,8 +3,10 @@ using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -35,6 +37,12 @@ namespace osu.Game.Skinning }; } + [Resolved] + private GameHost host { get; set; } + + [Resolved] + private AudioManager audio { get; set; } + [Resolved] private SkinManager skinManager { get; set; } @@ -60,6 +68,8 @@ namespace osu.Game.Skinning SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, Beatmap)); SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, Beatmap)); + + SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); } } } From ca3c45363a8d7c20b6e42c5676f38c7fb97b4515 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Jun 2021 23:59:59 +0300 Subject: [PATCH 0630/1621] Add test coverage --- .../TestSceneRulesetSkinProvidingContainer.cs | 60 +++++++++++++++++++ .../Testing/TestSceneRulesetDependencies.cs | 2 +- 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs new file mode 100644 index 0000000000..41f1465c90 --- /dev/null +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Rulesets; +using osu.Game.Skinning; +using osu.Game.Tests.Testing; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Rulesets +{ + public class TestSceneRulesetSkinProvidingContainer : OsuTestScene + { + private RulesetSkinProvidingContainer rulesetSkinProvider; + private SkinRequester requester; + + protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin) + .WithChild(requester = new SkinRequester()); + }); + + [Test] + public void TestRulesetResources() + { + AddAssert("ruleset texture retrieved via skin", () => requester.GetTexture("test-image") != null); + AddAssert("ruleset sample retrieved via skin", () => requester.GetSample(new SampleInfo("test-sample")) != null); + } + + private class SkinRequester : Drawable, ISkin + { + private ISkinSource skin; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + this.skin = skin; + } + + public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component); + + public Texture GetTexture(string componentName, WrapMode wrapModeS = default, WrapMode wrapModeT = default) => skin.GetTexture(componentName); + + public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); + + public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); + } + } +} diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs index 97087e31ab..fb50da32f3 100644 --- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs +++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Testing Dependencies.Get() != null); } - private class TestRuleset : Ruleset + public class TestRuleset : Ruleset { public override string Description => string.Empty; public override string ShortName => string.Empty; From fe48ce4bd5d80ec5b039a5a88a7fa5df9fcfa749 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 00:58:05 +0300 Subject: [PATCH 0631/1621] Remove unaccessed field It was a warning... --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 41f1465c90..e9f9766725 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -19,7 +19,6 @@ namespace osu.Game.Tests.Rulesets { public class TestSceneRulesetSkinProvidingContainer : OsuTestScene { - private RulesetSkinProvidingContainer rulesetSkinProvider; private SkinRequester requester; protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); @@ -27,7 +26,7 @@ namespace osu.Game.Tests.Rulesets [SetUp] public void SetUp() => Schedule(() => { - Child = rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin) + Child = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin) .WithChild(requester = new SkinRequester()); }); From 38bf04d7ff15df0575d9831baefb608f837d66a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 13:25:09 +0900 Subject: [PATCH 0632/1621] 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 0633/1621] 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 0634/1621] 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 0635/1621] 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 0636/1621] 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 0637/1621] 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 0638/1621] 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 0639/1621] 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 0640/1621] 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 0641/1621] 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 0642/1621] 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 0643/1621] 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 0644/1621] 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 0645/1621] 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 0646/1621] 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 1c67ef7c9190ae8bdfe36cce0ed6d6e5576b040c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 16:23:59 +0900 Subject: [PATCH 0647/1621] Make catchup clock support seeking --- .../Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs index 9e1a020eca..20d12d62a3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs @@ -34,7 +34,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public void Stop() => IsRunning = false; - public bool Seek(double position) => true; + public bool Seek(double position) + { + CurrentTime = position; + return true; + } public void ResetSpeedAdjustments() { From 75d825c85cb01be1a55e56e8a2adfb1dd745c4db Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 16:24:52 +0900 Subject: [PATCH 0648/1621] Dont control master clock from sync manager --- .../Multiplayer/Spectate/CatchUpSyncManager.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index efc12eaaa5..2d0e88238d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -63,7 +63,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } updateCatchup(); - updateMasterClock(); } /// @@ -133,21 +132,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } } } - - /// - /// Updates the master clock's running state. - /// - private void updateMasterClock() - { - bool anyInSync = playerClocks.Any(s => !s.IsCatchingUp); - - if (MasterClock.IsRunning != anyInSync) - { - if (anyInSync) - MasterClock.Start(); - else - MasterClock.Stop(); - } - } } } From a99cb79738f61d4cacbbda13c9b87283fba04733 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 16:25:45 +0900 Subject: [PATCH 0649/1621] Seek master clock on multi-spectator start --- .../Multiplayer/Spectate/CatchUpSyncManager.cs | 13 +++++++++++-- .../Multiplayer/Spectate/ISyncManager.cs | 6 ++++++ .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 14 ++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index 2d0e88238d..0f7466e44b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.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.Graphics; @@ -33,6 +34,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public IAdjustableClock MasterClock { get; } + public event Action ReadyToStart; + /// /// The player clocks. /// @@ -80,14 +83,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate int readyCount = playerClocks.Count(s => !s.WaitingOnFrames.Value); if (readyCount == playerClocks.Count) - return hasStarted = true; + return performStart(); if (readyCount > 0) { firstStartAttemptTime ??= Time.Current; if (Time.Current - firstStartAttemptTime > MAXIMUM_START_DELAY) - return hasStarted = true; + return performStart(); + } + + bool performStart() + { + ReadyToStart?.Invoke(); + return hasStarted = true; } return false; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs index bd698108f6..f59c1a75f7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.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.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate @@ -15,6 +16,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// IAdjustableClock MasterClock { get; } + /// + /// An event which is invoked when gameplay is ready to start. + /// + event Action ReadyToStart; + /// /// Adds an to manage. /// diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 983daac909..e401c45933 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -101,6 +101,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, leaderboardContainer.Add); + + syncManager.ReadyToStart += onReadyToStart; } protected override void LoadComplete() @@ -129,6 +131,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private bool isCandidateAudioSource([CanBeNull] ISpectatorPlayerClock clock) => clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames.Value; + private void onReadyToStart() + { + var startTime = instances.Where(i => i.Score != null) + .SelectMany(i => i.Score.Replay.Frames) + .Select(f => f.Time) + .DefaultIfEmpty(0) + .Max(); + + masterClockContainer.Seek(startTime); + masterClockContainer.Start(); + } + protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) { } From 139401a04a4813072362d6807271cc07e01b0e22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Jun 2021 16:27:31 +0900 Subject: [PATCH 0650/1621] 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 0651/1621] 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 0652/1621] 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 0653/1621] 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 0654/1621] 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 0655/1621] 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 0656/1621] 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 0657/1621] 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 0658/1621] 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 0659/1621] 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 9eaaac6bb790616db3dcb259669e2b5d48b74b39 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 17:59:27 +0900 Subject: [PATCH 0660/1621] Remove master clock state assertions --- .../OnlinePlay/TestSceneCatchUpSyncManager.cs | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs index d4e591cf09..6851df3832 100644 --- a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs +++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs @@ -31,32 +31,24 @@ namespace osu.Game.Tests.OnlinePlay } [Test] - public void TestMasterClockStartsWhenAllPlayerClocksHaveFrames() + public void TestPlayerClocksStartWhenAllHaveFrames() { setWaiting(() => player1, false); - assertMasterState(false); assertPlayerClockState(() => player1, false); assertPlayerClockState(() => player2, false); setWaiting(() => player2, false); - assertMasterState(true); assertPlayerClockState(() => player1, true); assertPlayerClockState(() => player2, true); } [Test] - public void TestMasterClockDoesNotStartWhenNoneReadyForMaximumDelayTime() - { - AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); - assertMasterState(false); - } - - [Test] - public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime() + public void TestReadyPlayersStartWhenReadyForMaximumDelayTime() { setWaiting(() => player1, false); AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); - assertMasterState(true); + assertPlayerClockState(() => player1, true); + assertPlayerClockState(() => player2, false); } [Test] @@ -153,9 +145,6 @@ namespace osu.Game.Tests.OnlinePlay private void setPlayerClockTime(Func playerClock, double offsetFromMaster) => AddStep($"set player clock {playerClock().Id} = master - {offsetFromMaster}", () => playerClock().Seek(master.CurrentTime - offsetFromMaster)); - private void assertMasterState(bool running) - => AddAssert($"master clock {(running ? "is" : "is not")} running", () => master.IsRunning == running); - private void assertCatchingUp(Func playerClock, bool catchingUp) => AddAssert($"player clock {playerClock().Id} {(catchingUp ? "is" : "is not")} catching up", () => playerClock().IsCatchingUp == catchingUp); @@ -201,6 +190,11 @@ namespace osu.Game.Tests.OnlinePlay private class TestManualClock : ManualClock, IAdjustableClock { + public TestManualClock() + { + IsRunning = true; + } + public void Start() => IsRunning = true; public void Stop() => IsRunning = false; From e9ebbd298d4c7129123e40c1f8b8352abfd9ed96 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 18:13:54 +0900 Subject: [PATCH 0661/1621] Add a few more tests --- .../TestSceneMultiSpectatorScreen.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index f5032fc2a5..35d096a92e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.Spectator; +using osu.Game.Rulesets.UI; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; @@ -224,6 +225,36 @@ namespace osu.Game.Tests.Visual.Multiplayer assertMuted(PLAYER_2_ID, true); } + [Test] + public void TestSpectatingDuringGameplay() + { + var players = new[] { PLAYER_1_ID, PLAYER_2_ID }; + + start(players); + sendFrames(players, 300); + + loadSpectateScreen(); + sendFrames(players, 300); + + AddUntilStep("playing from correct point in time", () => this.ChildrenOfType().All(r => r.FrameStableClock.CurrentTime > 30000)); + } + + [Test] + public void TestSpectatingDuringGameplayWithLateFrames() + { + start(new[] { PLAYER_1_ID, PLAYER_2_ID }); + sendFrames(new[] { PLAYER_1_ID, PLAYER_2_ID }, 300); + + loadSpectateScreen(); + sendFrames(PLAYER_1_ID, 300); + + AddWaitStep("wait maximum start delay seconds", (int)(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); + checkPaused(PLAYER_1_ID, false); + + sendFrames(PLAYER_2_ID, 300); + AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType().Single().FrameStableClock.CurrentTime > 30000); + } + private void loadSpectateScreen(bool waitForPlayerLoad = true) { AddStep("load screen", () => From 263b8ff097ed688e3fe0ff6e9bb90d1a17c93057 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 18:14:37 +0900 Subject: [PATCH 0662/1621] Wait for full player load --- osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index fe79e5db72..95ccc08608 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// Whether a is loaded in the area. /// - public bool PlayerLoaded => stack?.CurrentScreen is Player; + public bool PlayerLoaded => (stack?.CurrentScreen as Player)?.IsLoaded == true; /// /// The user id this corresponds to. From af3f253b2182784c99072e3df362cbb07714c97d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 11 Jun 2021 18:28:48 +0900 Subject: [PATCH 0663/1621] 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 0664/1621] 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 59eda70c12204f0ed71a551d711d1bb7aa1adbb3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 18:39:50 +0900 Subject: [PATCH 0665/1621] Seek to the least most-recent frame instead --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index e401c45933..1ea63e454a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -133,11 +133,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private void onReadyToStart() { + // Seek the master clock to the gameplay time. + // This is chosen as the first available frame in the players' replays, which matches the seek by each individual SpectatorPlayer. var startTime = instances.Where(i => i.Score != null) .SelectMany(i => i.Score.Replay.Frames) .Select(f => f.Time) .DefaultIfEmpty(0) - .Max(); + .Min(); masterClockContainer.Seek(startTime); masterClockContainer.Start(); From e59beffc4e7057254c4790a3b9112c5e6c09f3ff Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Jun 2021 12:44:25 +0300 Subject: [PATCH 0666/1621] 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 0667/1621] 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 0668/1621] 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 0a8daab4f78e369481e4bff7c382e2bd845386fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 19:15:53 +0900 Subject: [PATCH 0669/1621] Pause master clock when too far ahead --- .../TestSceneMultiSpectatorScreen.cs | 17 +++++++++++++++ .../Spectate/CatchUpSyncManager.cs | 21 ++++++++++++++++--- .../Multiplayer/Spectate/ISyncManager.cs | 10 +++++++-- .../Multiplayer/Spectate/MasterClockState.cs | 18 ++++++++++++++++ .../Spectate/MultiSpectatorScreen.cs | 10 +++++++++ 5 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 35d096a92e..18eb7e1c1e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -103,6 +103,23 @@ namespace osu.Game.Tests.Visual.Multiplayer AddWaitStep("wait a bit", 20); } + [Test] + public void TestTimeDoesNotProgressWhileAllPlayersPaused() + { + start(new[] { PLAYER_1_ID, PLAYER_2_ID }); + loadSpectateScreen(); + + sendFrames(PLAYER_1_ID, 20); + sendFrames(PLAYER_2_ID, 10); + + checkPaused(PLAYER_2_ID, true); + checkPausedInstant(PLAYER_1_ID, false); + AddAssert("master clock still running", () => this.ChildrenOfType().Single().IsRunning); + + checkPaused(PLAYER_1_ID, true); + AddUntilStep("master clock paused", () => !this.ChildrenOfType().Single().IsRunning); + } + [Test] public void TestPlayersMustStartSimultaneously() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index 0f7466e44b..781123f5bb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Timing; @@ -29,18 +30,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public const double MAXIMUM_START_DELAY = 15000; + public event Action ReadyToStart; + /// /// The master clock which is used to control the timing of all player clocks clocks. /// public IAdjustableClock MasterClock { get; } - public event Action ReadyToStart; + public IBindable MasterState => masterState; /// /// The player clocks. /// private readonly List playerClocks = new List(); + private readonly Bindable masterState = new Bindable(); + private bool hasStarted; private double? firstStartAttemptTime; @@ -65,7 +70,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return; } - updateCatchup(); + updatePlayerCatchup(); + updateMasterState(); } /// @@ -105,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// Updates the catchup states of all player clocks clocks. /// - private void updateCatchup() + private void updatePlayerCatchup() { for (int i = 0; i < playerClocks.Count; i++) { @@ -141,5 +147,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } } } + + /// + /// Updates the state of the master clock. + /// + private void updateMasterState() + { + bool anyInSync = playerClocks.Any(s => !s.IsCatchingUp); + masterState.Value = anyInSync ? MasterClockState.Synchronised : MasterClockState.TooFarAhead; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs index f59c1a75f7..3c644ccb78 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate @@ -11,15 +12,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public interface ISyncManager { + /// + /// An event which is invoked when gameplay is ready to start. + /// + event Action ReadyToStart; + /// /// The master clock which player clocks should synchronise to. /// IAdjustableClock MasterClock { get; } /// - /// An event which is invoked when gameplay is ready to start. + /// An event which is invoked when the state of is changed. /// - event Action ReadyToStart; + IBindable MasterState { get; } /// /// Adds an to manage. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.cs new file mode 100644 index 0000000000..8982d1669d --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.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. + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public enum MasterClockState + { + /// + /// The master clock is synchronised with at least one player clock. + /// + Synchronised, + + /// + /// The master clock is too far ahead of any player clock and needs to slow down. + /// + TooFarAhead + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 1ea63e454a..ee1968aa5f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; @@ -103,6 +104,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate }, leaderboardContainer.Add); syncManager.ReadyToStart += onReadyToStart; + syncManager.MasterState.BindValueChanged(onMasterStateChanged, true); } protected override void LoadComplete() @@ -145,6 +147,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate masterClockContainer.Start(); } + private void onMasterStateChanged(ValueChangedEvent state) + { + if (state.NewValue == MasterClockState.Synchronised) + masterClockContainer.Start(); + else + masterClockContainer.Stop(); + } + protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) { } 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 0670/1621] 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 0671/1621] 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 9f163f7f20ca155ab331d63d2fae99fdb1c2591d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Jun 2021 19:23:25 +0900 Subject: [PATCH 0672/1621] Use switch statement to be more explicit about state --- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index ee1968aa5f..013e5551cf 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -149,10 +149,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private void onMasterStateChanged(ValueChangedEvent state) { - if (state.NewValue == MasterClockState.Synchronised) - masterClockContainer.Start(); - else - masterClockContainer.Stop(); + switch (state.NewValue) + { + case MasterClockState.Synchronised: + masterClockContainer.Start(); + break; + + case MasterClockState.TooFarAhead: + masterClockContainer.Stop(); + break; + } } protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) 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 0673/1621] 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 0674/1621] 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 0675/1621] 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 0676/1621] 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 0677/1621] 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 0678/1621] 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 0679/1621] 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 0680/1621] 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 0681/1621] 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 0682/1621] 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 0683/1621] 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 0684/1621] 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 0685/1621] 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 0686/1621] 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 0687/1621] 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 0688/1621] 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 0689/1621] 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 0690/1621] 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 0691/1621] 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 0692/1621] 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 0693/1621] 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 0694/1621] 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 0695/1621] 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 0696/1621] 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 0697/1621] 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 0698/1621] 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 0699/1621] 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 0700/1621] 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 0701/1621] 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 0702/1621] 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 0703/1621] 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 0704/1621] 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 0705/1621] 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 0706/1621] 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 0707/1621] 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 0708/1621] 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 0709/1621] 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 0710/1621] 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 0711/1621] 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 0712/1621] 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 0713/1621] 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 0714/1621] 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 0715/1621] 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 0716/1621] 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 0717/1621] 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 0718/1621] 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 0719/1621] 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 0720/1621] 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 0721/1621] 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 0722/1621] 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 0723/1621] 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 0724/1621] 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 0725/1621] 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 04c0db6dceb94b147c5dd8dad095ed0d97e39adc Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 14 Jun 2021 21:34:34 +0800 Subject: [PATCH 0726/1621] Code cleanup --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 161 ++++++++++----------- osu.Game/Rulesets/Mods/IHasSeed.cs | 54 +++---- osu.Game/Rulesets/Mods/ModRandom.cs | 5 - 3 files changed, 105 insertions(+), 115 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 04a7365148..0d123a4ce3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -30,7 +30,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModTarget : ModWithVisibilityAdjustment, IApplicableToDrawableRuleset, - IApplicableToHealthProcessor, IApplicableToDifficulty, IApplicableFailOverride, IHasSeed + IApplicableToHealthProcessor, IApplicableToDifficulty, IApplicableFailOverride, IHasSeed { public override string Name => "Target"; public override string Acronym => "TP"; @@ -57,11 +57,11 @@ namespace osu.Game.Rulesets.Osu.Mods // Sudden death healthProcessor.FailConditions += (_, result) => result.Type.AffectsCombo() - && !result.IsHit; + && !result.IsHit; } // Maximum distance to jump - public const float MAX_DISTANCE = 250f; + private const float max_distance = 250f; public override void ApplyToBeatmap(IBeatmap beatmap) { @@ -69,76 +69,68 @@ namespace osu.Game.Rulesets.Osu.Mods var rng = new Random(Seed.Value.GetValueOrDefault()); - float nextSingle(float max = 1f) - { - return (float)(rng.NextDouble() * max); - } + float nextSingle(float max = 1f) => (float)(rng.NextDouble() * max); var osuBeatmap = (OsuBeatmap)beatmap; var origHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); // Only place circles between startTime and endTime var startTime = origHitObjects.First().StartTime; - double endTime; var endObj = origHitObjects.Last(); - switch (endObj) + var endTime = endObj switch { - case Slider slider: - endTime = slider.EndTime; - break; - case Spinner spinner: - endTime = spinner.EndTime; - break; - default: - endTime = endObj.StartTime; - break; - } + Slider slider => slider.EndTime, + Spinner spinner => spinner.EndTime, + _ => endObj.StartTime + }; // Generate the beats var beats = osuBeatmap.ControlPointInfo.TimingPoints - .Where(x => Precision.AlmostBigger(endTime, x.Time)) - .SelectMany(tp => - { - var tpBeats = new List(); - var currentTime = tp.Time; - while (Precision.AlmostBigger(endTime, currentTime) && osuBeatmap.ControlPointInfo.TimingPointAt(currentTime) == tp) - { - tpBeats.Add(currentTime); - currentTime += tp.BeatLength; - } - return tpBeats; - }) - // Remove beats that are before startTime - .Where(x => Precision.AlmostBigger(x, startTime)) - // Remove beats during breaks - .Where(x => !osuBeatmap.Breaks.Any(b => - Precision.AlmostBigger(x, b.StartTime) - && Precision.AlmostBigger(origHitObjects.First(y => Precision.AlmostBigger(y.StartTime, b.EndTime)).StartTime, x) - )) - .ToList(); + .Where(x => Precision.AlmostBigger(endTime, x.Time)) + .SelectMany(tp => + { + var tpBeats = new List(); + var currentTime = tp.Time; + + while (Precision.AlmostBigger(endTime, currentTime) && osuBeatmap.ControlPointInfo.TimingPointAt(currentTime) == tp) + { + tpBeats.Add(currentTime); + currentTime += tp.BeatLength; + } + + return tpBeats; + }) + // Remove beats that are before startTime + .Where(x => Precision.AlmostBigger(x, startTime)) + // Remove beats during breaks + .Where(x => !osuBeatmap.Breaks.Any(b => + Precision.AlmostBigger(x, b.StartTime) + && Precision.AlmostBigger(origHitObjects.First(y => Precision.AlmostBigger(y.StartTime, b.EndTime)).StartTime, x) + )) + .ToList(); // Generate a hit circle for each beat var hitObjects = beats - // Remove beats that are too close to the next one (e.g. due to timing point changes) - .Where((x, idx) => - { - if (idx == beats.Count - 1) return true; - if (Precision.AlmostBigger(osuBeatmap.ControlPointInfo.TimingPointAt(x).BeatLength / 2, beats[idx + 1] - x)) - return false; - return true; - }) - .Select(x => - { - var newCircle = new HitCircle(); - newCircle.ApplyDefaults(osuBeatmap.ControlPointInfo, osuBeatmap.BeatmapInfo.BaseDifficulty); - newCircle.StartTime = x; - return (OsuHitObject)newCircle; - }).ToList(); + // Remove beats that are too close to the next one (e.g. due to timing point changes) + .Where((x, idx) => + { + if (idx == beats.Count - 1) return true; + + return !Precision.AlmostBigger(osuBeatmap.ControlPointInfo.TimingPointAt(x).BeatLength / 2, beats[idx + 1] - x); + }) + .Select(x => + { + var newCircle = new HitCircle(); + newCircle.ApplyDefaults(osuBeatmap.ControlPointInfo, osuBeatmap.BeatmapInfo.BaseDifficulty); + newCircle.StartTime = x; + return (OsuHitObject)newCircle; + }).ToList(); // Add hit samples to the circles for (int i = 0; i < hitObjects.Count; i++) { var x = hitObjects[i]; var samples = getSamplesAtTime(origHitObjects, x.StartTime); + if (samples == null) { if (i > 0) @@ -155,11 +147,11 @@ namespace osu.Game.Rulesets.Osu.Mods hitObjects.ForEach(x => { var origObj = origHitObjects.FindLast(y => Precision.AlmostBigger(x.StartTime, y.StartTime)); - if (origObj == null) x.ComboIndex = 0; - else x.ComboIndex = origObj.ComboIndex; + x.ComboIndex = origObj?.ComboIndex ?? 0; }); // Then reprocess them to ensure continuity in the combo indices and add indices in current combo var combos = hitObjects.GroupBy(x => x.ComboIndex).ToList(); + for (int i = 0; i < combos.Count; i++) { var group = combos[i].ToList(); @@ -176,20 +168,22 @@ namespace osu.Game.Rulesets.Osu.Mods // Position all hit circles var direction = MathHelper.TwoPi * nextSingle(); + for (int i = 0; i < hitObjects.Count; i++) { var x = hitObjects[i]; + if (i == 0) { x.Position = new Vector2(nextSingle(OsuPlayfield.BASE_SIZE.X), nextSingle(OsuPlayfield.BASE_SIZE.Y)); } else { - var distance = Math.Min(MAX_DISTANCE, 40f * (float)Math.Pow(1.05, x.ComboIndex)); + var distance = Math.Min(max_distance, 40f * (float)Math.Pow(1.05, x.ComboIndex)); var relativePos = new Vector2( - distance * (float)Math.Cos(direction), - distance * (float)Math.Sin(direction) - ); + distance * (float)Math.Cos(direction), + distance * (float)Math.Sin(direction) + ); relativePos = getRotatedVector(hitObjects[i - 1].Position, relativePos); direction = (float)Math.Atan2(relativePos.Y, relativePos.X); @@ -209,7 +203,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (x.LastInCombo) direction = MathHelper.TwoPi * nextSingle(); else - direction += distance / MAX_DISTANCE * (nextSingle() * MathHelper.TwoPi - MathHelper.Pi); + direction += distance / max_distance * (nextSingle() * MathHelper.TwoPi - MathHelper.Pi); } } @@ -227,25 +221,24 @@ namespace osu.Game.Rulesets.Osu.Mods /// The list of hit objects in a beatmap, ordered by StartTime /// The point in time to get samples for /// Hit samples - private IList getSamplesAtTime(List hitObjects, double time) + private IList getSamplesAtTime(IEnumerable hitObjects, double time) { var sampleObj = hitObjects.FirstOrDefault(x => { if (Precision.AlmostEquals(time, x.StartTime)) return true; - if (x is Slider slider - && Precision.AlmostBigger(time, slider.StartTime) - && Precision.AlmostBigger(slider.EndTime, time)) - { - if (Precision.AlmostEquals((time - slider.StartTime) % slider.SpanDuration, 0)) - { - return true; - } - } - return false; + + if (!(x is Slider s)) + return false; + if (!Precision.AlmostBigger(time, s.StartTime) + || !Precision.AlmostBigger(s.EndTime, time)) + return false; + + return Precision.AlmostEquals((time - s.StartTime) % s.SpanDuration, 0); }); if (sampleObj == null) return null; - IList samples = null; + IList samples; + if (sampleObj is Slider slider) { samples = slider.NodeSamples[(int)Math.Round((time - slider.StartTime) % slider.SpanDuration)]; @@ -254,6 +247,7 @@ namespace osu.Game.Rulesets.Osu.Mods { samples = sampleObj.Samples; } + return samples; } @@ -269,19 +263,18 @@ namespace osu.Game.Rulesets.Osu.Mods var h = (OsuHitObject)drawable.HitObject; // apply grow and fade effect - if (drawable is DrawableHitCircle circle) + if (!(drawable is DrawableHitCircle circle)) return; + + using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) { - using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) - { - // todo: this doesn't feel quite right yet - drawable.ScaleTo(0.4f) + // todo: this doesn't feel quite right yet + drawable.ScaleTo(0.4f) .Then().ScaleTo(1.6f, h.TimePreempt * 2); - drawable.FadeTo(0.5f) + drawable.FadeTo(0.5f) .Then().Delay(h.TimeFadeIn).FadeTo(1f); - // remove approach circles - circle.ApproachCircle.Hide(); - } + // remove approach circles + circle.ApproachCircle.Hide(); } } @@ -347,7 +340,7 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Rotates vector "initial" towards vector "destinantion" + /// Rotates vector "initial" towards vector "destination" /// /// Vector to rotate to "destination" /// Vector "initial" should be rotated to @@ -399,7 +392,7 @@ namespace osu.Game.Rulesets.Osu.Mods { InternalChildren = new Drawable[] { - sample = new PausableSkinnableSound(new SampleInfo("spinnerbonus")), // todo: use another sample? + sample = new PausableSkinnableSound(new SampleInfo("spinnerbonus")) // todo: use another sample? }; } diff --git a/osu.Game/Rulesets/Mods/IHasSeed.cs b/osu.Game/Rulesets/Mods/IHasSeed.cs index 45a806e80c..13d59a9855 100644 --- a/osu.Game/Rulesets/Mods/IHasSeed.cs +++ b/osu.Game/Rulesets/Mods/IHasSeed.cs @@ -5,7 +5,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; -using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; @@ -18,11 +17,14 @@ namespace osu.Game.Rulesets.Mods public class SeedSettingsControl : SettingsItem { - protected override Drawable CreateControl() => new SeedControl + protected override Drawable CreateControl() { - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 5 } - }; + return new SeedControl + { + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 5 } + }; + } private sealed class SeedControl : CompositeDrawable, IHasCurrentValue { @@ -46,33 +48,33 @@ namespace osu.Game.Rulesets.Mods InternalChildren = new[] { - new GridContainer + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] + new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(GridSizeMode.Relative, 0.25f) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), - new Dimension(GridSizeMode.Relative, 0.25f) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] + seedNumberBox = new OsuNumberBox { - seedNumberBox = new OsuNumberBox - { - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true - } + RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true } } } - }; + } + }; seedNumberBox.Current.BindValueChanged(e => { diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index eec66161ff..61297c162d 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -2,14 +2,9 @@ // 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.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 { 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 0727/1621] 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 0728/1621] 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 0729/1621] 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 4ffff06dcbc0ac52d49d70643c5bd00d881d8c1c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 15 Jun 2021 11:06:56 +0800 Subject: [PATCH 0730/1621] Break ApplyToBeatmap into subroutines --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 116 ++++++++++++--------- 1 file changed, 67 insertions(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 0d123a4ce3..3bb4b0419f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -67,14 +67,31 @@ namespace osu.Game.Rulesets.Osu.Mods { Seed.Value ??= RNG.Next(); - var rng = new Random(Seed.Value.GetValueOrDefault()); - - float nextSingle(float max = 1f) => (float)(rng.NextDouble() * max); - var osuBeatmap = (OsuBeatmap)beatmap; var origHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); - // Only place circles between startTime and endTime + var hitObjects = generateBeats(osuBeatmap, origHitObjects) + .Select(x => + { + var newCircle = new HitCircle(); + newCircle.ApplyDefaults(osuBeatmap.ControlPointInfo, osuBeatmap.BeatmapInfo.BaseDifficulty); + newCircle.StartTime = x; + return (OsuHitObject)newCircle; + }).ToList(); + + addHitSamples(hitObjects, origHitObjects); + + fixComboInfo(hitObjects, origHitObjects); + + randomizeCirclePos(hitObjects); + + osuBeatmap.HitObjects = hitObjects; + + base.ApplyToBeatmap(beatmap); + } + + private IEnumerable generateBeats(IBeatmap beatmap, IReadOnlyCollection origHitObjects) + { var startTime = origHitObjects.First().StartTime; var endObj = origHitObjects.Last(); var endTime = endObj switch @@ -84,48 +101,45 @@ namespace osu.Game.Rulesets.Osu.Mods _ => endObj.StartTime }; - // Generate the beats - var beats = osuBeatmap.ControlPointInfo.TimingPoints - .Where(x => Precision.AlmostBigger(endTime, x.Time)) - .SelectMany(tp => - { - var tpBeats = new List(); - var currentTime = tp.Time; + var beats = beatmap.ControlPointInfo.TimingPoints + .Where(x => Precision.AlmostBigger(endTime, x.Time)) + .SelectMany(tp => + { + var tpBeats = new List(); + var currentTime = tp.Time; - while (Precision.AlmostBigger(endTime, currentTime) && osuBeatmap.ControlPointInfo.TimingPointAt(currentTime) == tp) - { - tpBeats.Add(currentTime); - currentTime += tp.BeatLength; - } + while (Precision.AlmostBigger(endTime, currentTime) && beatmap.ControlPointInfo.TimingPointAt(currentTime) == tp) + { + tpBeats.Add(currentTime); + currentTime += tp.BeatLength; + } - return tpBeats; - }) - // Remove beats that are before startTime - .Where(x => Precision.AlmostBigger(x, startTime)) - // Remove beats during breaks - .Where(x => !osuBeatmap.Breaks.Any(b => - Precision.AlmostBigger(x, b.StartTime) - && Precision.AlmostBigger(origHitObjects.First(y => Precision.AlmostBigger(y.StartTime, b.EndTime)).StartTime, x) - )) - .ToList(); - // Generate a hit circle for each beat - var hitObjects = beats - // Remove beats that are too close to the next one (e.g. due to timing point changes) - .Where((x, idx) => - { - if (idx == beats.Count - 1) return true; + return tpBeats; + }) + .Where(x => Precision.AlmostBigger(x, startTime)) + // Remove beats during breaks + .Where(x => !beatmap.Breaks.Any(b => + Precision.AlmostBigger(x, b.StartTime) + && Precision.AlmostBigger(origHitObjects.First(y => Precision.AlmostBigger(y.StartTime, b.EndTime)).StartTime, x) + )) + .ToList(); - return !Precision.AlmostBigger(osuBeatmap.ControlPointInfo.TimingPointAt(x).BeatLength / 2, beats[idx + 1] - x); - }) - .Select(x => - { - var newCircle = new HitCircle(); - newCircle.ApplyDefaults(osuBeatmap.ControlPointInfo, osuBeatmap.BeatmapInfo.BaseDifficulty); - newCircle.StartTime = x; - return (OsuHitObject)newCircle; - }).ToList(); + // Remove beats that are too close to the next one (e.g. due to timing point changes) + for (int i = beats.Count - 2; i >= 0; i--) + { + var beat = beats[i]; - // Add hit samples to the circles + if (Precision.AlmostBigger(beatmap.ControlPointInfo.TimingPointAt(beat).BeatLength / 2, beats[i + 1] - beat)) + { + beats.RemoveAt(i); + } + } + + return beats; + } + + private void addHitSamples(IReadOnlyList hitObjects, IReadOnlyCollection origHitObjects) + { for (int i = 0; i < hitObjects.Count; i++) { var x = hitObjects[i]; @@ -141,8 +155,10 @@ namespace osu.Game.Rulesets.Osu.Mods x.Samples = samples; } } + } - // Process combo numbers + private void fixComboInfo(List hitObjects, List origHitObjects) + { // First follow the combo indices in the original beatmap hitObjects.ForEach(x => { @@ -165,8 +181,14 @@ namespace osu.Game.Rulesets.Osu.Mods x.IndexInCurrentCombo = j; } } + } + + private void randomizeCirclePos(IReadOnlyList hitObjects) + { + var rng = new Random(Seed.Value.GetValueOrDefault()); + + float nextSingle(float max = 1f) => (float)(rng.NextDouble() * max); - // Position all hit circles var direction = MathHelper.TwoPi * nextSingle(); for (int i = 0; i < hitObjects.Count; i++) @@ -206,10 +228,6 @@ namespace osu.Game.Rulesets.Osu.Mods direction += distance / max_distance * (nextSingle() * MathHelper.TwoPi - MathHelper.Pi); } } - - osuBeatmap.HitObjects = hitObjects; - - base.ApplyToBeatmap(beatmap); } /// From bbf00226898dd7c374a17712866f3d8fa40266f1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 15 Jun 2021 13:11:07 +0900 Subject: [PATCH 0731/1621] 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 0732/1621] 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 0733/1621] 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 0734/1621] 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 0735/1621] 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 0736/1621] 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 0737/1621] 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 0738/1621] 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 0739/1621] 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 0740/1621] 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 0741/1621] 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 0742/1621] 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 0743/1621] 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 0744/1621] 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 0745/1621] 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 0746/1621] 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 0747/1621] 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 0748/1621] 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 0749/1621] 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 0750/1621] 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 0751/1621] 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 0752/1621] 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 0753/1621] 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 0754/1621] 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 0755/1621] 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 0756/1621] 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 0757/1621] 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 0758/1621] 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 0759/1621] 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 0760/1621] 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 0761/1621] 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 0762/1621] 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 0763/1621] 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 0764/1621] 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 0765/1621] 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 0766/1621] 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 0767/1621] 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 0768/1621] 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 0769/1621] 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 0770/1621] 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 0771/1621] 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 0772/1621] 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 0773/1621] 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 0774/1621] 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 0775/1621] 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 0776/1621] 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 0777/1621] 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 0778/1621] 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 0779/1621] 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 0780/1621] 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 0781/1621] 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 0782/1621] 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 0783/1621] 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 0784/1621] 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 0785/1621] 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 0786/1621] 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 0787/1621] 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 0788/1621] 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 0789/1621] 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 0790/1621] 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 0791/1621] 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 0792/1621] 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 0793/1621] 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 0794/1621] 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 0795/1621] 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 0796/1621] 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 0797/1621] 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 0798/1621] 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 0799/1621] 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 0800/1621] 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 0801/1621] 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 0802/1621] 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 0803/1621] 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 14622f473442cdf43a9539f669903fd4f9ba3f57 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 17 Jun 2021 10:20:50 +0800 Subject: [PATCH 0804/1621] Improved guesstimations; fixed hit samples --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 3bb4b0419f..b748cdf4a2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Mods while (Precision.AlmostBigger(endTime, currentTime) && beatmap.ControlPointInfo.TimingPointAt(currentTime) == tp) { - tpBeats.Add(currentTime); + tpBeats.Add(Math.Floor(currentTime)); currentTime += tp.BeatLength; } @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (samples == null) { if (i > 0) - x.Samples = hitObjects[i - 1].Samples; + x.Samples = hitObjects[i - 1].Samples.Where(s => !HitSampleInfo.AllAdditions.Contains(s.Name)).ToList(); } else { @@ -289,7 +289,7 @@ namespace osu.Game.Rulesets.Osu.Mods drawable.ScaleTo(0.4f) .Then().ScaleTo(1.6f, h.TimePreempt * 2); drawable.FadeTo(0.5f) - .Then().Delay(h.TimeFadeIn).FadeTo(1f); + .Then().Delay(h.TimePreempt * 2 / 3).FadeTo(1f); // remove approach circles circle.ApproachCircle.Hide(); @@ -304,6 +304,7 @@ namespace osu.Game.Rulesets.Osu.Mods { // Decrease AR to increase preempt time difficulty.ApproachRate *= 0.5f; + difficulty.CircleSize *= 0.75f; } // The distances from the hit objects to the borders of the playfield they start to "turn around" and curve towards the middle. From 249ae3141edb47ba78b1a994b53652015bfd3637 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Jun 2021 15:06:56 +0900 Subject: [PATCH 0805/1621] 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 0806/1621] 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 0807/1621] 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 f22beaeb5b4e63decc9fa0e0837920e6d496fbc2 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 17 Jun 2021 14:30:59 +0800 Subject: [PATCH 0808/1621] Increase distance between combos; pull circles closer to center --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index b748cdf4a2..e91b2cd9d7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -193,15 +193,17 @@ namespace osu.Game.Rulesets.Osu.Mods for (int i = 0; i < hitObjects.Count; i++) { - var x = hitObjects[i]; + var obj = hitObjects[i]; if (i == 0) { - x.Position = new Vector2(nextSingle(OsuPlayfield.BASE_SIZE.X), nextSingle(OsuPlayfield.BASE_SIZE.Y)); + obj.Position = new Vector2(nextSingle(OsuPlayfield.BASE_SIZE.X), nextSingle(OsuPlayfield.BASE_SIZE.Y)); } else { - var distance = Math.Min(max_distance, 40f * (float)Math.Pow(1.05, x.ComboIndex)); + var distance = 40f * (float)Math.Pow(1.05, obj.ComboIndex); + if (obj.NewCombo) distance *= 1.5f; + distance = Math.Min(max_distance, distance); var relativePos = new Vector2( distance * (float)Math.Cos(direction), distance * (float)Math.Sin(direction) @@ -220,9 +222,9 @@ namespace osu.Game.Rulesets.Osu.Mods else if (newPosition.X > OsuPlayfield.BASE_SIZE.X) newPosition.X = OsuPlayfield.BASE_SIZE.X; - x.Position = newPosition; + obj.Position = newPosition; - if (x.LastInCombo) + if (obj.LastInCombo) direction = MathHelper.TwoPi * nextSingle(); else direction += distance / max_distance * (nextSingle() * MathHelper.TwoPi - MathHelper.Pi); @@ -354,7 +356,7 @@ namespace osu.Game.Rulesets.Osu.Mods return rotateVectorTowardsVector( posRelativeToPrev, Vector2.Subtract(playfieldMiddle, prevPosChanged), - relativeRotationDistance / 2 + relativeRotationDistance * 0.75f ); } From 8da0431fa076307fcaad9443304a98ef83bdb7cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Jun 2021 16:05:50 +0900 Subject: [PATCH 0809/1621] 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 0810/1621] 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 0811/1621] 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 0812/1621] 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 0813/1621] 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 0814/1621] 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 0815/1621] 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 0816/1621] 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 0817/1621] 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 0818/1621] 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 b7f43405fc3d9175db86cf165ced58a1a97ec057 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 17 Jun 2021 22:01:58 +0800 Subject: [PATCH 0819/1621] Dim circles instead of fade; improved hit samples; changed jump distance to be closer to cuttingedge --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 268 +++++++++++---------- 1 file changed, 138 insertions(+), 130 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index e91b2cd9d7..c9e43704e1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -26,6 +26,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods { @@ -46,12 +47,23 @@ namespace osu.Game.Rulesets.Osu.Mods Value = null }; - public bool PerformFail() => true; - public bool RestartOnFail => false; public bool DisplayResultsOnFail => true; + // Maximum distance to jump + private const float max_distance = 250f; + + // The distances from the hit objects to the borders of the playfield they start to "turn around" and curve towards the middle. + // The closer the hit objects draw to the border, the sharper the turn + private const byte border_distance_x = 192; + private const byte border_distance_y = 144; + + public bool PerformFail() + { + return true; + } + public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { // Sudden death @@ -60,9 +72,6 @@ namespace osu.Game.Rulesets.Osu.Mods && !result.IsHit; } - // Maximum distance to jump - private const float max_distance = 250f; - public override void ApplyToBeatmap(IBeatmap beatmap) { Seed.Value ??= RNG.Next(); @@ -90,6 +99,55 @@ namespace osu.Game.Rulesets.Osu.Mods base.ApplyToBeatmap(beatmap); } + public void ReadFromDifficulty(BeatmapDifficulty difficulty) + { + } + + public void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + // Decrease AR to increase preempt time + difficulty.ApproachRate *= 0.5f; + difficulty.CircleSize *= 0.75f; + } + + // Background metronome + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + drawableRuleset.Overlays.Add(new TargetBeatContainer()); + } + + protected override void ApplyIncreasedVisibilityState(DrawableHitObject drawable, ArmedState state) + { + } + + protected override void ApplyNormalVisibilityState(DrawableHitObject drawable, ArmedState state) + { + if (!(drawable is DrawableHitCircle circle)) return; + + var h = (OsuHitObject)drawable.HitObject; + + using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) + { + drawable.ScaleTo(0.5f) + .Then().ScaleTo(1f, h.TimePreempt); + + var colour = drawable.Colour; + + var avgColour = colour.AverageColour.Linear; + drawable.FadeColour(new Color4(avgColour.R * 0.45f, avgColour.G * 0.45f, avgColour.B * 0.45f, avgColour.A)) + .Then().Delay(h.TimeFadeIn).FadeColour(colour); + + // remove approach circles + circle.ApproachCircle.Hide(); + } + } + + private static float map(float value, float fromLow, float fromHigh, float toLow, float toHigh) + { + return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow; + } + private IEnumerable generateBeats(IBeatmap beatmap, IReadOnlyCollection origHitObjects) { var startTime = origHitObjects.First().StartTime; @@ -125,35 +183,43 @@ namespace osu.Game.Rulesets.Osu.Mods .ToList(); // Remove beats that are too close to the next one (e.g. due to timing point changes) - for (int i = beats.Count - 2; i >= 0; i--) + for (var i = beats.Count - 2; i >= 0; i--) { var beat = beats[i]; - if (Precision.AlmostBigger(beatmap.ControlPointInfo.TimingPointAt(beat).BeatLength / 2, beats[i + 1] - beat)) - { - beats.RemoveAt(i); - } + if (Precision.AlmostBigger(beatmap.ControlPointInfo.TimingPointAt(beat).BeatLength / 2, beats[i + 1] - beat)) beats.RemoveAt(i); } return beats; } - private void addHitSamples(IReadOnlyList hitObjects, IReadOnlyCollection origHitObjects) + private void addHitSamples(IEnumerable hitObjects, IReadOnlyList origHitObjects) { - for (int i = 0; i < hitObjects.Count; i++) + var lastSampleIdx = 0; + + foreach (var x in hitObjects) { - var x = hitObjects[i]; var samples = getSamplesAtTime(origHitObjects, x.StartTime); if (samples == null) { - if (i > 0) - x.Samples = hitObjects[i - 1].Samples.Where(s => !HitSampleInfo.AllAdditions.Contains(s.Name)).ToList(); + while (lastSampleIdx < origHitObjects.Count && origHitObjects[lastSampleIdx].StartTime <= x.StartTime) + lastSampleIdx++; + lastSampleIdx--; + + if (lastSampleIdx < 0 && lastSampleIdx >= origHitObjects.Count) continue; + + if (lastSampleIdx < origHitObjects.Count - 1) + { + // get samples from the next hit object if it is closer in time + if (origHitObjects[lastSampleIdx + 1].StartTime - x.StartTime < x.StartTime - origHitObjects[lastSampleIdx].StartTime) + lastSampleIdx++; + } + + x.Samples = origHitObjects[lastSampleIdx].Samples.Where(s => !HitSampleInfo.AllAdditions.Contains(s.Name)).ToList(); } else - { x.Samples = samples; - } } } @@ -168,13 +234,13 @@ namespace osu.Game.Rulesets.Osu.Mods // Then reprocess them to ensure continuity in the combo indices and add indices in current combo var combos = hitObjects.GroupBy(x => x.ComboIndex).ToList(); - for (int i = 0; i < combos.Count; i++) + for (var i = 0; i < combos.Count; i++) { var group = combos[i].ToList(); group.First().NewCombo = true; group.Last().LastInCombo = true; - for (int j = 0; j < group.Count; j++) + for (var j = 0; j < group.Count; j++) { var x = group[j]; x.ComboIndex = i; @@ -190,53 +256,52 @@ namespace osu.Game.Rulesets.Osu.Mods float nextSingle(float max = 1f) => (float)(rng.NextDouble() * max); var direction = MathHelper.TwoPi * nextSingle(); + var maxComboIndex = hitObjects.Last().ComboIndex; - for (int i = 0; i < hitObjects.Count; i++) + for (var i = 0; i < hitObjects.Count; i++) { var obj = hitObjects[i]; + var lastPos = i == 0 + ? Vector2.Divide(OsuPlayfield.BASE_SIZE, 2) + : hitObjects[i - 1].Position; - if (i == 0) - { - obj.Position = new Vector2(nextSingle(OsuPlayfield.BASE_SIZE.X), nextSingle(OsuPlayfield.BASE_SIZE.Y)); - } + var distance = map(obj.ComboIndex, 0, maxComboIndex, (float)obj.Radius, 333f); + if (obj.NewCombo) distance *= 1.5f; + if (obj.Kiai) distance *= 1.2f; + distance = Math.Min(max_distance, distance); + + var relativePos = new Vector2( + distance * (float)Math.Cos(direction), + distance * (float)Math.Sin(direction) + ); + relativePos = getRotatedVector(lastPos, relativePos); + direction = (float)Math.Atan2(relativePos.Y, relativePos.X); + + var newPosition = Vector2.Add(lastPos, relativePos); + + if (newPosition.Y < 0) + newPosition.Y = 0; + else if (newPosition.Y > OsuPlayfield.BASE_SIZE.Y) + newPosition.Y = OsuPlayfield.BASE_SIZE.Y; + if (newPosition.X < 0) + newPosition.X = 0; + else if (newPosition.X > OsuPlayfield.BASE_SIZE.X) + newPosition.X = OsuPlayfield.BASE_SIZE.X; + + obj.Position = newPosition; + + if (obj.LastInCombo) + direction = MathHelper.TwoPi * nextSingle(); else - { - var distance = 40f * (float)Math.Pow(1.05, obj.ComboIndex); - if (obj.NewCombo) distance *= 1.5f; - distance = Math.Min(max_distance, distance); - var relativePos = new Vector2( - distance * (float)Math.Cos(direction), - distance * (float)Math.Sin(direction) - ); - relativePos = getRotatedVector(hitObjects[i - 1].Position, relativePos); - direction = (float)Math.Atan2(relativePos.Y, relativePos.X); - - var newPosition = Vector2.Add(hitObjects[i - 1].Position, relativePos); - - if (newPosition.Y < 0) - newPosition.Y = 0; - else if (newPosition.Y > OsuPlayfield.BASE_SIZE.Y) - newPosition.Y = OsuPlayfield.BASE_SIZE.Y; - if (newPosition.X < 0) - newPosition.X = 0; - else if (newPosition.X > OsuPlayfield.BASE_SIZE.X) - newPosition.X = OsuPlayfield.BASE_SIZE.X; - - obj.Position = newPosition; - - if (obj.LastInCombo) - direction = MathHelper.TwoPi * nextSingle(); - else - direction += distance / max_distance * (nextSingle() * MathHelper.TwoPi - MathHelper.Pi); - } + direction += distance / max_distance * (nextSingle() * MathHelper.TwoPi - MathHelper.Pi); } } /// - /// Get samples (if any) for a specific point in time. + /// Get samples (if any) for a specific point in time. /// /// - /// Samples will be returned if a hit circle or a slider node exists at that point of time. + /// Samples will be returned if a hit circle or a slider node exists at that point of time. /// /// The list of hit objects in a beatmap, ordered by StartTime /// The point in time to get samples for @@ -260,62 +325,15 @@ namespace osu.Game.Rulesets.Osu.Mods IList samples; if (sampleObj is Slider slider) - { samples = slider.NodeSamples[(int)Math.Round((time - slider.StartTime) % slider.SpanDuration)]; - } else - { samples = sampleObj.Samples; - } return samples; } - protected override void ApplyIncreasedVisibilityState(DrawableHitObject drawable, ArmedState state) - { - } - - protected override void ApplyNormalVisibilityState(DrawableHitObject drawable, ArmedState state) - { - if (drawable is DrawableSpinner) - return; - - var h = (OsuHitObject)drawable.HitObject; - - // apply grow and fade effect - if (!(drawable is DrawableHitCircle circle)) return; - - using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) - { - // todo: this doesn't feel quite right yet - drawable.ScaleTo(0.4f) - .Then().ScaleTo(1.6f, h.TimePreempt * 2); - drawable.FadeTo(0.5f) - .Then().Delay(h.TimePreempt * 2 / 3).FadeTo(1f); - - // remove approach circles - circle.ApproachCircle.Hide(); - } - } - - public void ReadFromDifficulty(BeatmapDifficulty difficulty) - { - } - - public void ApplyToDifficulty(BeatmapDifficulty difficulty) - { - // Decrease AR to increase preempt time - difficulty.ApproachRate *= 0.5f; - difficulty.CircleSize *= 0.75f; - } - - // The distances from the hit objects to the borders of the playfield they start to "turn around" and curve towards the middle. - // The closer the hit objects draw to the border, the sharper the turn - private const byte border_distance_x = 192; - private const byte border_distance_y = 144; - /// - /// Determines the position of the current hit object relative to the previous one. + /// Determines the position of the current hit object relative to the previous one. /// /// The position of the current hit object relative to the previous one private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev) @@ -356,16 +374,19 @@ namespace osu.Game.Rulesets.Osu.Mods return rotateVectorTowardsVector( posRelativeToPrev, Vector2.Subtract(playfieldMiddle, prevPosChanged), - relativeRotationDistance * 0.75f + Math.Min(1, relativeRotationDistance * 0.75f) ); } /// - /// Rotates vector "initial" towards vector "destination" + /// Rotates vector "initial" towards vector "destination" /// /// Vector to rotate to "destination" /// Vector "initial" should be rotated to - /// The angle the vector should be rotated relative to the difference between the angles of the the two vectors. + /// + /// The angle the vector should be rotated relative to the difference between the angles of + /// the the two vectors. + /// /// Resulting vector private Vector2 rotateVectorTowardsVector(Vector2 initial, Vector2 destination, float relativeDistance) { @@ -374,15 +395,9 @@ namespace osu.Game.Rulesets.Osu.Mods var diff = destAngleRad - initialAngleRad; - while (diff < -Math.PI) - { - diff += 2 * Math.PI; - } + while (diff < -Math.PI) diff += 2 * Math.PI; - while (diff > Math.PI) - { - diff -= 2 * Math.PI; - } + while (diff > Math.PI) diff -= 2 * Math.PI; var finalAngleRad = initialAngleRad + relativeDistance * diff; @@ -392,13 +407,6 @@ namespace osu.Game.Rulesets.Osu.Mods ); } - // Background metronome - - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - drawableRuleset.Overlays.Add(new TargetBeatContainer()); - } - public class TargetBeatContainer : BeatSyncedContainer { private PausableSkinnableSound sample; @@ -408,15 +416,6 @@ namespace osu.Game.Rulesets.Osu.Mods Divisor = 1; } - [BackgroundDependencyLoader] - private void load() - { - InternalChildren = new Drawable[] - { - sample = new PausableSkinnableSound(new SampleInfo("spinnerbonus")) // todo: use another sample? - }; - } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); @@ -425,6 +424,15 @@ namespace osu.Game.Rulesets.Osu.Mods sample?.Play(); } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + sample = new PausableSkinnableSound(new SampleInfo("spinnerbonus")) // todo: use another sample + }; + } } } } 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 0820/1621] 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 0821/1621] 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 0822/1621] 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 0823/1621] 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 639e8b62b94c37cadb3d9bf84da159efa20a9e36 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 18 Jun 2021 11:20:04 +0800 Subject: [PATCH 0824/1621] Make circles light up 1 beat length before start time --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index c9e43704e1..db6d483dee 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -59,6 +59,8 @@ namespace osu.Game.Rulesets.Osu.Mods private const byte border_distance_x = 192; private const byte border_distance_y = 144; + private ControlPointInfo controlPointInfo; + public bool PerformFail() { return true; @@ -77,13 +79,14 @@ namespace osu.Game.Rulesets.Osu.Mods Seed.Value ??= RNG.Next(); var osuBeatmap = (OsuBeatmap)beatmap; + controlPointInfo = osuBeatmap.ControlPointInfo; var origHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); var hitObjects = generateBeats(osuBeatmap, origHitObjects) .Select(x => { var newCircle = new HitCircle(); - newCircle.ApplyDefaults(osuBeatmap.ControlPointInfo, osuBeatmap.BeatmapInfo.BaseDifficulty); + newCircle.ApplyDefaults(controlPointInfo, osuBeatmap.BeatmapInfo.BaseDifficulty); newCircle.StartTime = x; return (OsuHitObject)newCircle; }).ToList(); @@ -136,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Mods var avgColour = colour.AverageColour.Linear; drawable.FadeColour(new Color4(avgColour.R * 0.45f, avgColour.G * 0.45f, avgColour.B * 0.45f, avgColour.A)) - .Then().Delay(h.TimeFadeIn).FadeColour(colour); + .Then().Delay(h.TimePreempt - controlPointInfo.TimingPointAt(h.StartTime).BeatLength).FadeColour(colour); // remove approach circles circle.ApproachCircle.Hide(); From c9458fd9cec36e9d9407f4210fa39bf9bbef4ab1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Jun 2021 04:57:29 +0300 Subject: [PATCH 0825/1621] 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 0826/1621] 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 0827/1621] 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 0828/1621] 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 0829/1621] 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 dca2d8af4f7c2027f4f874237cc6aadf809cc16c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 18 Jun 2021 13:18:44 +0800 Subject: [PATCH 0830/1621] Animate circles undimming --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index db6d483dee..b7463103df 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Mods var avgColour = colour.AverageColour.Linear; drawable.FadeColour(new Color4(avgColour.R * 0.45f, avgColour.G * 0.45f, avgColour.B * 0.45f, avgColour.A)) - .Then().Delay(h.TimePreempt - controlPointInfo.TimingPointAt(h.StartTime).BeatLength).FadeColour(colour); + .Then().Delay(h.TimePreempt - controlPointInfo.TimingPointAt(h.StartTime).BeatLength - 96).FadeColour(colour, 96); // remove approach circles circle.ApproachCircle.Hide(); From f5134c7fc279d804067ecd8d30b291f654994a02 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 18 Jun 2021 14:39:46 +0800 Subject: [PATCH 0831/1621] Extract constants and add xmldoc --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 25 ++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index b7463103df..63fda42393 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -51,14 +51,26 @@ namespace osu.Game.Rulesets.Osu.Mods public bool DisplayResultsOnFail => true; - // Maximum distance to jump - private const float max_distance = 250f; + /// + /// Jump distance for circles in the last combo + /// + private const float max_base_distance = 250f; + + /// + /// The maximum allowed jump distance after multipliers are applied + /// + private const float distance_cap = 350f; // The distances from the hit objects to the borders of the playfield they start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn private const byte border_distance_x = 192; private const byte border_distance_y = 144; + /// + /// Duration of the undimming animation + /// + private const double undim_duration = 96; + private ControlPointInfo controlPointInfo; public bool PerformFail() @@ -139,7 +151,8 @@ namespace osu.Game.Rulesets.Osu.Mods var avgColour = colour.AverageColour.Linear; drawable.FadeColour(new Color4(avgColour.R * 0.45f, avgColour.G * 0.45f, avgColour.B * 0.45f, avgColour.A)) - .Then().Delay(h.TimePreempt - controlPointInfo.TimingPointAt(h.StartTime).BeatLength - 96).FadeColour(colour, 96); + .Then().Delay(h.TimePreempt - controlPointInfo.TimingPointAt(h.StartTime).BeatLength - undim_duration) + .FadeColour(colour, undim_duration); // remove approach circles circle.ApproachCircle.Hide(); @@ -268,10 +281,10 @@ namespace osu.Game.Rulesets.Osu.Mods ? Vector2.Divide(OsuPlayfield.BASE_SIZE, 2) : hitObjects[i - 1].Position; - var distance = map(obj.ComboIndex, 0, maxComboIndex, (float)obj.Radius, 333f); + var distance = map(obj.ComboIndex, 0, maxComboIndex, (float)obj.Radius, max_base_distance); if (obj.NewCombo) distance *= 1.5f; if (obj.Kiai) distance *= 1.2f; - distance = Math.Min(max_distance, distance); + distance = Math.Min(distance_cap, distance); var relativePos = new Vector2( distance * (float)Math.Cos(direction), @@ -296,7 +309,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (obj.LastInCombo) direction = MathHelper.TwoPi * nextSingle(); else - direction += distance / max_distance * (nextSingle() * MathHelper.TwoPi - MathHelper.Pi); + direction += distance / distance_cap * (nextSingle() * MathHelper.TwoPi - MathHelper.Pi); } } From 5cf2ac78fcc1790b2556d11336c10496c7a94da8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Jun 2021 15:40:35 +0900 Subject: [PATCH 0832/1621] 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 0833/1621] 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 0834/1621] 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 0835/1621] 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 0836/1621] 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 0837/1621] 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 0838/1621] 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 0839/1621] 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 0840/1621] 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 0841/1621] 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 cca26d465110153254eeac9e2fbd078a4f771e5e Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 18 Jun 2021 16:05:09 +0800 Subject: [PATCH 0842/1621] Take circle radius into account when clamping hit objects to playfield --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 43 +++++++++++++--------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 63fda42393..de7309c27f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -52,12 +52,12 @@ namespace osu.Game.Rulesets.Osu.Mods public bool DisplayResultsOnFail => true; /// - /// Jump distance for circles in the last combo + /// Jump distance for circles in the last combo /// private const float max_base_distance = 250f; /// - /// The maximum allowed jump distance after multipliers are applied + /// The maximum allowed jump distance after multipliers are applied /// private const float distance_cap = 350f; @@ -67,7 +67,12 @@ namespace osu.Game.Rulesets.Osu.Mods private const byte border_distance_y = 144; /// - /// Duration of the undimming animation + /// The extent of rotation towards playfield centre when a circle is near the edge + /// + private const float edge_rotation_multiplier = 0.75f; + + /// + /// Duration of the undimming animation /// private const double undim_duration = 96; @@ -295,14 +300,16 @@ namespace osu.Game.Rulesets.Osu.Mods var newPosition = Vector2.Add(lastPos, relativePos); - if (newPosition.Y < 0) - newPosition.Y = 0; - else if (newPosition.Y > OsuPlayfield.BASE_SIZE.Y) - newPosition.Y = OsuPlayfield.BASE_SIZE.Y; - if (newPosition.X < 0) - newPosition.X = 0; - else if (newPosition.X > OsuPlayfield.BASE_SIZE.X) - newPosition.X = OsuPlayfield.BASE_SIZE.X; + var radius = (float)obj.Radius; + + if (newPosition.Y < radius) + newPosition.Y = radius; + else if (newPosition.Y > OsuPlayfield.BASE_SIZE.Y - radius) + newPosition.Y = OsuPlayfield.BASE_SIZE.Y - radius; + if (newPosition.X < radius) + newPosition.X = radius; + else if (newPosition.X > OsuPlayfield.BASE_SIZE.X - radius) + newPosition.X = OsuPlayfield.BASE_SIZE.X - radius; obj.Position = newPosition; @@ -314,10 +321,10 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Get samples (if any) for a specific point in time. + /// Get samples (if any) for a specific point in time. /// /// - /// Samples will be returned if a hit circle or a slider node exists at that point of time. + /// Samples will be returned if a hit circle or a slider node exists at that point of time. /// /// The list of hit objects in a beatmap, ordered by StartTime /// The point in time to get samples for @@ -349,7 +356,7 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Determines the position of the current hit object relative to the previous one. + /// Determines the position of the current hit object relative to the previous one. /// /// The position of the current hit object relative to the previous one private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev) @@ -390,18 +397,18 @@ namespace osu.Game.Rulesets.Osu.Mods return rotateVectorTowardsVector( posRelativeToPrev, Vector2.Subtract(playfieldMiddle, prevPosChanged), - Math.Min(1, relativeRotationDistance * 0.75f) + Math.Min(1, relativeRotationDistance * edge_rotation_multiplier) ); } /// - /// Rotates vector "initial" towards vector "destination" + /// Rotates vector "initial" towards vector "destination" /// /// Vector to rotate to "destination" /// Vector "initial" should be rotated to /// - /// The angle the vector should be rotated relative to the difference between the angles of - /// the the two vectors. + /// The angle the vector should be rotated relative to the difference between the angles of + /// the the two vectors. /// /// Resulting vector private Vector2 rotateVectorTowardsVector(Vector2 initial, Vector2 destination, float relativeDistance) From d06e52505a9b8105a8683ea31506a96f69528d26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 17:01:51 +0900 Subject: [PATCH 0843/1621] 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 0844/1621] 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 0845/1621] 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 0846/1621] 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 0847/1621] 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 0848/1621] 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 0849/1621] 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 0850/1621] 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 0851/1621] 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 0852/1621] 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 0853/1621] 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 0854/1621] 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 0855/1621] 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 0856/1621] 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 0857/1621] 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 0858/1621] 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 0859/1621] 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 0860/1621] 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 0861/1621] 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 0862/1621] 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 0863/1621] 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 0864/1621] 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 0865/1621] 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 0866/1621] 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 ca8f08ca84894258478ff86a283aa8729986c433 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 19 Jun 2021 10:04:48 +0800 Subject: [PATCH 0867/1621] Avoid overlapping with recent circles --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 62 +++++++++++++++------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index de7309c27f..4e4e798f47 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -54,12 +54,12 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Jump distance for circles in the last combo /// - private const float max_base_distance = 250f; + private const float max_base_distance = 333f; /// /// The maximum allowed jump distance after multipliers are applied /// - private const float distance_cap = 350f; + private const float distance_cap = 380f; // The distances from the hit objects to the borders of the playfield they start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn @@ -71,6 +71,11 @@ namespace osu.Game.Rulesets.Osu.Mods ///
private const float edge_rotation_multiplier = 0.75f; + /// + /// Number of recent circles to check for overlap + /// + private const int overlap_check_count = 5; + /// /// Duration of the undimming animation /// @@ -291,27 +296,39 @@ namespace osu.Game.Rulesets.Osu.Mods if (obj.Kiai) distance *= 1.2f; distance = Math.Min(distance_cap, distance); - var relativePos = new Vector2( - distance * (float)Math.Cos(direction), - distance * (float)Math.Sin(direction) - ); - relativePos = getRotatedVector(lastPos, relativePos); - direction = (float)Math.Atan2(relativePos.Y, relativePos.X); + // Attempt to place the circle at a place that does not overlap with previous ones - var newPosition = Vector2.Add(lastPos, relativePos); + var tryCount = 0; - var radius = (float)obj.Radius; + do + { + if (tryCount > 0) direction = MathHelper.TwoPi * nextSingle(); - if (newPosition.Y < radius) - newPosition.Y = radius; - else if (newPosition.Y > OsuPlayfield.BASE_SIZE.Y - radius) - newPosition.Y = OsuPlayfield.BASE_SIZE.Y - radius; - if (newPosition.X < radius) - newPosition.X = radius; - else if (newPosition.X > OsuPlayfield.BASE_SIZE.X - radius) - newPosition.X = OsuPlayfield.BASE_SIZE.X - radius; + var relativePos = new Vector2( + distance * (float)Math.Cos(direction), + distance * (float)Math.Sin(direction) + ); + relativePos = getRotatedVector(lastPos, relativePos); + direction = (float)Math.Atan2(relativePos.Y, relativePos.X); - obj.Position = newPosition; + var newPosition = Vector2.Add(lastPos, relativePos); + + var radius = (float)obj.Radius; + + if (newPosition.Y < radius) + newPosition.Y = radius; + else if (newPosition.Y > OsuPlayfield.BASE_SIZE.Y - radius) + newPosition.Y = OsuPlayfield.BASE_SIZE.Y - radius; + if (newPosition.X < radius) + newPosition.X = radius; + else if (newPosition.X > OsuPlayfield.BASE_SIZE.X - radius) + newPosition.X = OsuPlayfield.BASE_SIZE.X - radius; + + obj.Position = newPosition; + + tryCount++; + if (tryCount % 10 == 0) distance *= 0.9f; + } while (distance >= obj.Radius * 2 && isOverlappingWithRecent(hitObjects, i)); if (obj.LastInCombo) direction = MathHelper.TwoPi * nextSingle(); @@ -355,6 +372,13 @@ namespace osu.Game.Rulesets.Osu.Mods return samples; } + private bool isOverlappingWithRecent(IReadOnlyList hitObjects, int idx) + { + var target = hitObjects[idx]; + return hitObjects.SkipLast(hitObjects.Count - idx).TakeLast(overlap_check_count) + .Any(h => Vector2.Distance(h.Position, target.Position) < target.Radius * 2); + } + /// /// Determines the position of the current hit object relative to the previous one. /// From 0cf3119006f8a0a9ec8d4513e646ca4705320bcf Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 19 Jun 2021 11:12:29 +0800 Subject: [PATCH 0868/1621] Guard against edge cases --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 4e4e798f47..0e7dc7a9d3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -101,6 +101,9 @@ namespace osu.Game.Rulesets.Osu.Mods Seed.Value ??= RNG.Next(); var osuBeatmap = (OsuBeatmap)beatmap; + + if (osuBeatmap.HitObjects.Count == 0) return; + controlPointInfo = osuBeatmap.ControlPointInfo; var origHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); @@ -277,6 +280,8 @@ namespace osu.Game.Rulesets.Osu.Mods private void randomizeCirclePos(IReadOnlyList hitObjects) { + if (hitObjects.Count == 0) return; + var rng = new Random(Seed.Value.GetValueOrDefault()); float nextSingle(float max = 1f) => (float)(rng.NextDouble() * max); @@ -291,7 +296,9 @@ namespace osu.Game.Rulesets.Osu.Mods ? Vector2.Divide(OsuPlayfield.BASE_SIZE, 2) : hitObjects[i - 1].Position; - var distance = map(obj.ComboIndex, 0, maxComboIndex, (float)obj.Radius, max_base_distance); + var distance = maxComboIndex == 0 + ? (float)obj.Radius + : map(obj.ComboIndex, 0, maxComboIndex, (float)obj.Radius, max_base_distance); if (obj.NewCombo) distance *= 1.5f; if (obj.Kiai) distance *= 1.2f; distance = Math.Min(distance_cap, distance); From b09165a074c11661f656836f946c6a5ddd1f13a0 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 19 Jun 2021 11:13:19 +0800 Subject: [PATCH 0869/1621] Remove the circle size buff --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 0e7dc7a9d3..9688bc7154 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -135,7 +135,6 @@ namespace osu.Game.Rulesets.Osu.Mods { // Decrease AR to increase preempt time difficulty.ApproachRate *= 0.5f; - difficulty.CircleSize *= 0.75f; } // Background metronome From c86794058442186d219391fdcf05d396c8651775 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 19 Jun 2021 12:26:16 +0800 Subject: [PATCH 0870/1621] Marked target mod and traceable mod as incompatible; extracted playfield clamping logic Nothing is visible when target mod and traceable mod are enabled together. --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 33 ++++++++++++------- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 2 +- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 9688bc7154..a6476541ab 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Practice keeping up with the beat of the song."; public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable) }; + [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SeedSettingsControl))] public Bindable Seed { get; } = new Bindable { @@ -319,19 +321,10 @@ namespace osu.Game.Rulesets.Osu.Mods var newPosition = Vector2.Add(lastPos, relativePos); - var radius = (float)obj.Radius; - - if (newPosition.Y < radius) - newPosition.Y = radius; - else if (newPosition.Y > OsuPlayfield.BASE_SIZE.Y - radius) - newPosition.Y = OsuPlayfield.BASE_SIZE.Y - radius; - if (newPosition.X < radius) - newPosition.X = radius; - else if (newPosition.X > OsuPlayfield.BASE_SIZE.X - radius) - newPosition.X = OsuPlayfield.BASE_SIZE.X - radius; - obj.Position = newPosition; + clampToPlayfield(obj); + tryCount++; if (tryCount % 10 == 0) distance *= 0.9f; } while (distance >= obj.Radius * 2 && isOverlappingWithRecent(hitObjects, i)); @@ -460,6 +453,24 @@ namespace osu.Game.Rulesets.Osu.Mods ); } + private void clampToPlayfield(OsuHitObject obj) + { + var position = obj.Position; + var radius = (float)obj.Radius; + + if (position.Y < radius) + position.Y = radius; + else if (position.Y > OsuPlayfield.BASE_SIZE.Y - radius) + position.Y = OsuPlayfield.BASE_SIZE.Y - radius; + + if (position.X < radius) + position.X = radius; + else if (position.X > OsuPlayfield.BASE_SIZE.X - radius) + position.X = OsuPlayfield.BASE_SIZE.X - radius; + + obj.Position = position; + } + public class TargetBeatContainer : BeatSyncedContainer { private PausableSkinnableSound sample; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 4b0939db16..c5b0a2da3e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -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(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween), typeof(OsuModTarget) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { 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 0871/1621] 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 0872/1621] 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 0873/1621] 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 0874/1621] 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 0875/1621] 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 27da3dc75a408b44ddf4dc1a38f4f0a9e55b8a57 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sat, 19 Jun 2021 20:54:24 +0800 Subject: [PATCH 0876/1621] added supporter-only-filter content --- .../BeatmapListingFilterControl.cs | 10 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 114 +++++++++++++++++- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 1935a250b7..3436a1b3b2 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -24,6 +24,7 @@ namespace osu.Game.Overlays.BeatmapListing { /// /// Fired when a search finishes. Contains only new items in the case of pagination. + /// Null when non-supporter user used supporter-only filters /// public Action> SearchFinished; @@ -212,7 +213,14 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse = response; getSetsRequest = null; - SearchFinished?.Invoke(sets); + if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) + { + SearchFinished?.Invoke(null); + } + else + { + SearchFinished?.Invoke(sets); + } }; api.Queue(getSetsRequest); diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5e65cd9488..63b9d3d34a 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -15,7 +15,9 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; @@ -33,6 +35,7 @@ namespace osu.Game.Overlays private Container panelTarget; private FillFlowContainer foundContent; private NotFoundDrawable notFoundContent; + private SupporterRequiredDrawable supporterRequiredContent; private BeatmapListingFilterControl filterControl; public BeatmapListingOverlay() @@ -76,6 +79,7 @@ namespace osu.Game.Overlays { foundContent = new FillFlowContainer(), notFoundContent = new NotFoundDrawable(), + supporterRequiredContent = new SupporterRequiredDrawable(), } } }, @@ -117,6 +121,13 @@ namespace osu.Game.Overlays private void onSearchFinished(List beatmaps) { + // non-supporter user used supporter-only filters + if (beatmaps == null) + { + LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + return; + } + var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, @@ -170,7 +181,7 @@ namespace osu.Game.Overlays { var transform = lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent) + if (lastContent == notFoundContent || lastContent == supporterRequiredContent) { // not found display may be used multiple times, so don't expire/dispose it. transform.Schedule(() => panelTarget.Remove(lastContent)); @@ -240,6 +251,107 @@ namespace osu.Game.Overlays } } + public class SupporterRequiredDrawable : CompositeDrawable + { + public SupporterRequiredDrawable() + { + RelativeSizeAxes = Axes.X; + Height = 250; + Alpha = 0; + Margin = new MarginPadding { Top = 15 }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + AddInternal(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Texture = textures.Get(@"Online/supporter-required"), + }, + createSupportRequiredText(), + } + }); + } + + private Drawable createSupportRequiredText() + { + LinkFlowContainer linkFlowContainer; + string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault( + BeatmapsStrings.ListingSearchFiltersRank.ToString(), + "{1}" + ).ToString().Split("{1}"); + + // var titleContainer = new Container + // { + // RelativeSizeAxes = Axes.X, + // Margin = new MarginPadding { Vertical = 5 }, + // Children = new Drawable[] + // { + // linkFlowContainer = new LinkFlowContainer + // { + // Anchor = Anchor.Centre, + // Origin = Anchor.Centre, + // } + // } + // }; + + linkFlowContainer = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding + { + Bottom = 10, + } + }; + + linkFlowContainer.AddText( + text[0], + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.White; + } + ); + + linkFlowContainer.AddLink( + BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), + "https://osu.ppy.sh/store/products/supporter-tag", + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.AliceBlue; + } + ); + + linkFlowContainer.AddText( + text[1], + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.White; + } + ); + + return linkFlowContainer; + } + } + private const double time_between_fetches = 500; private double lastFetchDisplayedTime; From c04b09520da8115080bec4dfc4310e9f549493e3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 19 Jun 2021 20:06:28 +0300 Subject: [PATCH 0877/1621] 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 0878/1621] 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 0879/1621] 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 0880/1621] 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 0881/1621] 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 42fdfbb9a1d2504da309c96ca23cdee4829819b2 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 17:17:07 +0800 Subject: [PATCH 0882/1621] added visual tests --- .../Online/TestSceneBeatmapListingOverlay.cs | 74 +++++++++++++++++++ osu.Game/Overlays/BeatmapListingOverlay.cs | 32 +++----- 2 files changed, 83 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 156d6b744e..86008ce173 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -14,6 +14,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Rulesets; +using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { @@ -58,6 +59,79 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } + [Test] + public void TestSupporterOnlyFiltersPlaceholder() { + + AddStep("toggle non-supporter", () => + { + // non-supporter user + ((DummyAPIAccess)API).LocalUser.Value = new User + { + Username = API.LocalUser.Value.Username, + Id = API.LocalUser.Value.Id + 1, + IsSupporter = false, + }; + }); + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + + AddStep("toggle Random Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); + overlay.ChildrenOfType().Single().Ranks.Add(r); + // overlay.ChildrenOfType().Single().Ranks. + }); + + AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + AddStep("Clear Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("toggle Random Played filter", () => { + SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); + overlay.ChildrenOfType().Single().Played.Value = r; + }); + + AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + AddStep("Clear Played filter", () => { + overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("toggle supporter", () => + { + // supporter user + ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true; + }); + + AddStep("toggle Random Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); + overlay.ChildrenOfType().Single().Ranks.Add(r); + }); + + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("toggle Random Played filter", () => { + SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); + overlay.ChildrenOfType().Single().Played.Value = r; + }); + + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Played filter", () => { + overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 63b9d3d34a..3dec6de03a 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -181,11 +181,16 @@ namespace osu.Game.Overlays { var transform = lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent || lastContent == supporterRequiredContent) + if (lastContent == notFoundContent) { // not found display may be used multiple times, so don't expire/dispose it. transform.Schedule(() => panelTarget.Remove(lastContent)); } + else if (lastContent == supporterRequiredContent) + { + // supporter required display may be used multiple times, so don't expire/dispose it. + transform.Schedule(() => panelTarget.Remove(supporterRequiredContent)); + } else { // Consider the case when the new content is smaller than the last content. @@ -256,9 +261,8 @@ namespace osu.Game.Overlays public SupporterRequiredDrawable() { RelativeSizeAxes = Axes.X; - Height = 250; + Height = 225; Alpha = 0; - Margin = new MarginPadding { Top = 15 }; } [BackgroundDependencyLoader] @@ -271,7 +275,6 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), Children = new Drawable[] { new Sprite @@ -290,24 +293,7 @@ namespace osu.Game.Overlays private Drawable createSupportRequiredText() { LinkFlowContainer linkFlowContainer; - string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault( - BeatmapsStrings.ListingSearchFiltersRank.ToString(), - "{1}" - ).ToString().Split("{1}"); - - // var titleContainer = new Container - // { - // RelativeSizeAxes = Axes.X, - // Margin = new MarginPadding { Vertical = 5 }, - // Children = new Drawable[] - // { - // linkFlowContainer = new LinkFlowContainer - // { - // Anchor = Anchor.Centre, - // Origin = Anchor.Centre, - // } - // } - // }; + string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(BeatmapsStrings.ListingSearchFiltersRank.ToString(), "{1}").ToString().Split("{1}"); linkFlowContainer = new LinkFlowContainer { @@ -335,7 +321,7 @@ namespace osu.Game.Overlays t => { t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.AliceBlue; + t.Colour = Colour4.FromHex("#A6C8D9"); } ); From e7aeba8d039cd8b3dfcaad98ed440d1cb4e9f620 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 18:28:43 +0800 Subject: [PATCH 0883/1621] added more visual tests --- .../Online/TestSceneBeatmapListingOverlay.cs | 153 +++++++++++------- .../BeatmapListingFilterControl.cs | 1 + 2 files changed, 99 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 86008ce173..eebaea545a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -40,6 +40,16 @@ namespace osu.Game.Tests.Visual.Online return true; }; + + AddStep("initialize dummy", () => + { + // non-supporter user + ((DummyAPIAccess)API).LocalUser.Value = new User + { + Username = "TestBot", + Id = API.LocalUser.Value.Id + 1, + }; + }); } [Test] @@ -60,78 +70,95 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupporterOnlyFiltersPlaceholder() { + public void TestSupporterOnlyFiltersPlaceholderNoBeatmaps() + { + AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); - AddStep("toggle non-supporter", () => - { - // non-supporter user - ((DummyAPIAccess)API).LocalUser.Value = new User - { - Username = API.LocalUser.Value.Username, - Id = API.LocalUser.Value.Id + 1, - IsSupporter = false, - }; - }); - AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + // test non-supporter on Rank Achieved filter + toggleRandomRankFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddStep("toggle Random Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); - overlay.ChildrenOfType().Single().Ranks.Add(r); - // overlay.ChildrenOfType().Single().Ranks. - }); - - AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - - AddStep("Clear Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - }); + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("toggle Random Played filter", () => { - SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); - overlay.ChildrenOfType().Single().Played.Value = r; - }); + // test non-supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - - AddStep("Clear Played filter", () => { - overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; - }); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("toggle supporter", () => - { - // supporter user - ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true; - }); - - AddStep("toggle Random Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); - overlay.ChildrenOfType().Single().Ranks.Add(r); - }); + AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); + // test supporter on Rank Achieved filter + toggleRandomRankFilter(); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("Clear Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - }); + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("toggle Random Played filter", () => { - SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); - overlay.ChildrenOfType().Single().Played.Value = r; - }); - + // test supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("Clear Played filter", () => { - overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; - }); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } + [Test] + public void TestSupporterOnlyFiltersPlaceholderOneBeatmap() + { + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); + + // test non-supporter on Rank Achieved filter + toggleRandomRankFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + // test non-supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); + + // test supporter on Rank Achieved filter + toggleRandomRankFilter(); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + // test supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + + private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); @@ -141,6 +168,22 @@ namespace osu.Game.Tests.Visual.Online overlay.ChildrenOfType().Single().Query.TriggerChange(); } + private void toggleRandomRankFilter() + { + short r = TestContext.CurrentContext.Random.NextShort(); + AddStep("toggle Random Rank Achieved filter", () => + { + overlay.ChildrenOfType().Single().Ranks.Clear(); + overlay.ChildrenOfType().Single().Ranks.Add((Scoring.ScoreRank)(r % 8)); + }); + } + + private void toggleRandomSupporterOnlyPlayedFilter() + { + short r = TestContext.CurrentContext.Random.NextShort(); + AddStep("toggle Random Played filter", () => overlay.ChildrenOfType().Single().Played.Value = (SearchPlayed)(r % 2 + 1)); + } + private class TestAPIBeatmapSet : APIBeatmapSet { private readonly BeatmapSetInfo beatmapSet; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 3436a1b3b2..7b7f742b73 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -213,6 +213,7 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse = response; getSetsRequest = null; + // check if an non-supporter user used supporter-only filters if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) { SearchFinished?.Invoke(null); From 996503eb2d350aeae8252e9504541c82300da234 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 21:23:54 +0800 Subject: [PATCH 0884/1621] fixed filter text display, added visual tests --- .../Online/TestSceneBeatmapListingOverlay.cs | 106 ++++++++++++------ .../BeatmapListingFilterControl.cs | 8 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 45 ++++---- 3 files changed, 103 insertions(+), 56 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index eebaea545a..9128f72d6e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -72,45 +72,56 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestSupporterOnlyFiltersPlaceholderNoBeatmaps() { + AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); // test non-supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); + + // test non-supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(true, false); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + expectedPlaceholderShown(false, true); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // test supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); // test supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); + + // test supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(false, true); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + expectedPlaceholderShown(false, true); } [Test] @@ -121,41 +132,51 @@ namespace osu.Game.Tests.Visual.Online // test non-supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); // test non-supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); + + // test non-supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(true, false); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + expectedPlaceholderShown(false, false); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // test supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); // test supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); + + // test supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(false, false); + + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + expectedPlaceholderShown(false, false); } @@ -184,6 +205,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("toggle Random Played filter", () => overlay.ChildrenOfType().Single().Played.Value = (SearchPlayed)(r % 2 + 1)); } + private void expectedPlaceholderShown(bool supporterRequiredShown, bool notFoundShown) + { + if (supporterRequiredShown) + { + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + else + { + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + + if (notFoundShown) + { + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + else + { + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + } + private class TestAPIBeatmapSet : APIBeatmapSet { private readonly BeatmapSetInfo beatmapSet; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 7b7f742b73..6e83dc0bf4 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -24,9 +24,9 @@ namespace osu.Game.Overlays.BeatmapListing { /// /// Fired when a search finishes. Contains only new items in the case of pagination. - /// Null when non-supporter user used supporter-only filters + /// Fired with BeatmapListingSearchControl when non-supporter user used supporter-only filters. /// - public Action> SearchFinished; + public Action, BeatmapListingSearchControl> SearchFinished; /// /// Fired when search criteria change. @@ -216,11 +216,11 @@ namespace osu.Game.Overlays.BeatmapListing // check if an non-supporter user used supporter-only filters if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) { - SearchFinished?.Invoke(null); + SearchFinished?.Invoke(sets, searchControl); } else { - SearchFinished?.Invoke(sets); + SearchFinished?.Invoke(sets, null); } }; diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 3dec6de03a..90f1e6932d 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -119,11 +119,17 @@ namespace osu.Game.Overlays private Task panelLoadDelegate; - private void onSearchFinished(List beatmaps) + private void onSearchFinished(List beatmaps, BeatmapListingSearchControl searchControl) { // non-supporter user used supporter-only filters - if (beatmaps == null) + if (searchControl != null) { + // compose filter string + List filtersStrs = new List(); + if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); + if (searchControl.Played.Value != SearchPlayed.Any) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); + supporterRequiredContent.UpdateSupportRequiredText(string.Join(" and ", filtersStrs)); + LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); return; } @@ -258,11 +264,24 @@ namespace osu.Game.Overlays public class SupporterRequiredDrawable : CompositeDrawable { + private LinkFlowContainer linkFlowContainer; + public SupporterRequiredDrawable() { RelativeSizeAxes = Axes.X; Height = 225; Alpha = 0; + + linkFlowContainer = linkFlowContainer = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding + { + Bottom = 10, + } + }; } [BackgroundDependencyLoader] @@ -285,27 +304,15 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - createSupportRequiredText(), + linkFlowContainer, } }); } - private Drawable createSupportRequiredText() - { - LinkFlowContainer linkFlowContainer; - string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(BeatmapsStrings.ListingSearchFiltersRank.ToString(), "{1}").ToString().Split("{1}"); - - linkFlowContainer = new LinkFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding - { - Bottom = 10, - } - }; + public void UpdateSupportRequiredText(string filtersStr) { + string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(filtersStr, "{1}").ToString().Split("{1}"); + linkFlowContainer.Clear(); linkFlowContainer.AddText( text[0], t => @@ -333,8 +340,6 @@ namespace osu.Game.Overlays t.Colour = Colour4.White; } ); - - return linkFlowContainer; } } From d0a8b748238dfa03b9174db624c34bac0259ad2a Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 21:28:57 +0800 Subject: [PATCH 0885/1621] fixed filter text order --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 90f1e6932d..ceb033380c 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -126,8 +126,8 @@ namespace osu.Game.Overlays { // compose filter string List filtersStrs = new List(); - if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); if (searchControl.Played.Value != SearchPlayed.Any) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); + if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); supporterRequiredContent.UpdateSupportRequiredText(string.Join(" and ", filtersStrs)); LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); From 67f0344c0c93f082ddaedf920fd741d2b25faff7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 20 Jun 2021 23:07:44 +0300 Subject: [PATCH 0886/1621] Add support for loading shaders from ruleset resources --- .../UI/DrawableRulesetDependencies.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index e66a8c016c..b910f708a2 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -10,6 +10,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Platform; @@ -34,6 +35,11 @@ namespace osu.Game.Rulesets.UI /// public ISampleStore SampleStore { get; } + /// + /// The shader manager to be used for the ruleset. + /// + public ShaderManager ShaderManager { get; } + /// /// The ruleset config manager. /// @@ -52,6 +58,9 @@ namespace osu.Game.Rulesets.UI SampleStore = parent.Get().GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY; CacheAs(SampleStore = new FallbackSampleStore(SampleStore, parent.Get())); + + ShaderManager = new ShaderManager(new NamespacedResourceStore(resources, @"Shaders")); + CacheAs(ShaderManager = new FallbackShaderManager(ShaderManager, parent.Get())); } RulesetConfigManager = parent.Get().GetConfigFor(ruleset); @@ -84,6 +93,7 @@ namespace osu.Game.Rulesets.UI SampleStore?.Dispose(); TextureStore?.Dispose(); + ShaderManager?.Dispose(); RulesetConfigManager = null; } @@ -172,5 +182,25 @@ namespace osu.Game.Rulesets.UI primary?.Dispose(); } } + + private class FallbackShaderManager : ShaderManager + { + private readonly ShaderManager primary; + private readonly ShaderManager fallback; + + public FallbackShaderManager(ShaderManager primary, ShaderManager fallback) + { + this.primary = primary; + this.fallback = fallback; + } + + public override byte[] LoadRaw(string name) => primary.LoadRaw(name) ?? fallback.LoadRaw(name); + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + primary?.Dispose(); + } + } } } From eabcbd1d424761d762d8d4a8fb860956db6f1049 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 20 Jun 2021 23:11:10 +0300 Subject: [PATCH 0887/1621] Consider shader manager for ruleset dependencies disposal testing --- .../TestSceneDrawableRulesetDependencies.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index f421a30283..94cf317c20 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -13,6 +13,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; using osu.Framework.Testing; @@ -31,12 +32,14 @@ namespace osu.Game.Tests.Rulesets DrawableWithDependencies drawable = null; TestTextureStore textureStore = null; TestSampleStore sampleStore = null; + TestShaderManager shaderManager = null; AddStep("add dependencies", () => { Child = drawable = new DrawableWithDependencies(); textureStore = drawable.ParentTextureStore; sampleStore = drawable.ParentSampleStore; + shaderManager = drawable.ParentShaderManager; }); AddStep("clear children", Clear); @@ -52,12 +55,14 @@ namespace osu.Game.Tests.Rulesets AddAssert("parent texture store not disposed", () => !textureStore.IsDisposed); AddAssert("parent sample store not disposed", () => !sampleStore.IsDisposed); + AddAssert("parent shader manager not disposed", () => !shaderManager.IsDisposed); } private class DrawableWithDependencies : CompositeDrawable { public TestTextureStore ParentTextureStore { get; private set; } public TestSampleStore ParentSampleStore { get; private set; } + public TestShaderManager ParentShaderManager { get; private set; } public DrawableWithDependencies() { @@ -70,6 +75,7 @@ namespace osu.Game.Tests.Rulesets dependencies.CacheAs(ParentTextureStore = new TestTextureStore()); dependencies.CacheAs(ParentSampleStore = new TestSampleStore()); + dependencies.CacheAs(ParentShaderManager = new TestShaderManager()); return new DrawableRulesetDependencies(new OsuRuleset(), dependencies); } @@ -135,5 +141,18 @@ namespace osu.Game.Tests.Rulesets public int PlaybackConcurrency { get; set; } } + + private class TestShaderManager : ShaderManager + { + public override byte[] LoadRaw(string name) => null; + + public bool IsDisposed { get; private set; } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + IsDisposed = true; + } + } } } From c933cbe89d0aedf6ddbef2c0f320595973a1aec5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 00:09:47 +0300 Subject: [PATCH 0888/1621] Add sample shaders and test case for ruleset-specific shaders --- .../Resources/Shaders/sh_TestFragment.fs | 11 +++++++ .../Resources/Shaders/sh_TestVertex.vs | 31 +++++++++++++++++++ .../Testing/TestSceneRulesetDependencies.cs | 9 ++++++ 3 files changed, 51 insertions(+) create mode 100644 osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs create mode 100644 osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs diff --git a/osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs b/osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs new file mode 100644 index 0000000000..c70ad751be --- /dev/null +++ b/osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs @@ -0,0 +1,11 @@ +#include "sh_Utils.h" + +varying mediump vec2 v_TexCoord; +varying mediump vec4 v_TexRect; + +void main(void) +{ + float hueValue = v_TexCoord.x / (v_TexRect[2] - v_TexRect[0]); + gl_FragColor = hsv2rgb(vec4(hueValue, 1, 1, 1)); +} + diff --git a/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs b/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs new file mode 100644 index 0000000000..4485356fa4 --- /dev/null +++ b/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs @@ -0,0 +1,31 @@ +#include "sh_Utils.h" + +attribute highp vec2 m_Position; +attribute lowp vec4 m_Colour; +attribute mediump vec2 m_TexCoord; +attribute mediump vec4 m_TexRect; +attribute mediump vec2 m_BlendRange; + +varying highp vec2 v_MaskingPosition; +varying lowp vec4 v_Colour; +varying mediump vec2 v_TexCoord; +varying mediump vec4 v_TexRect; +varying mediump vec2 v_BlendRange; + +uniform highp mat4 g_ProjMatrix; +uniform highp mat3 g_ToMaskingSpace; + +void main(void) +{ + // Transform from screen space to masking space. + highp vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0); + v_MaskingPosition = maskingPos.xy / maskingPos.z; + + v_Colour = m_Colour; + v_TexCoord = m_TexCoord; + v_TexRect = m_TexRect; + v_BlendRange = m_BlendRange; + + gl_Position = gProjMatrix * vec4(m_Position, 1.0, 1.0); +} + diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs index 97087e31ab..433022788b 100644 --- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs +++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Configuration.Tracking; +using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Testing; @@ -45,6 +46,14 @@ namespace osu.Game.Tests.Testing Dependencies.Get().Get(@"test-sample") != null); } + [Test] + public void TestRetrieveShader() + { + AddAssert("ruleset shaders retrieved", () => + Dependencies.Get().LoadRaw(@"sh_TestVertex.vs") != null && + Dependencies.Get().LoadRaw(@"sh_TestFragment.fs") != null); + } + [Test] public void TestResolveConfigManager() { From 72155a7c525818431fef2c76565eb3995a1f769a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 03:37:49 +0300 Subject: [PATCH 0889/1621] 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 0890/1621] 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 0891/1621] 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 0892/1621] 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 0893/1621] 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 0894/1621] 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 0895/1621] 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 0896/1621] 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 0897/1621] 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 0898/1621] 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 0707642b76d94b81f3ef061bc6f207f7edf85764 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 14:25:20 +0800 Subject: [PATCH 0899/1621] fixed SupporterRequiredDrawable --- osu.Game/Overlays/BeatmapListingOverlay.cs | 89 +++++++++------------- 1 file changed, 34 insertions(+), 55 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index ceb033380c..5ddad1c9fc 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -121,25 +122,20 @@ namespace osu.Game.Overlays private void onSearchFinished(List beatmaps, BeatmapListingSearchControl searchControl) { - // non-supporter user used supporter-only filters - if (searchControl != null) - { - // compose filter string - List filtersStrs = new List(); - if (searchControl.Played.Value != SearchPlayed.Any) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); - if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); - supporterRequiredContent.UpdateSupportRequiredText(string.Join(" and ", filtersStrs)); - - LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); - return; - } - var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }); + // non-supporter user used supporter-only filters + if (searchControl != null) + { + supporterRequiredContent.UpdateText(searchControl.Played.Value != SearchPlayed.Any, searchControl.Ranks.Any()); + LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + return; + } + if (filterControl.CurrentPage == 0) { //No matches case @@ -262,26 +258,16 @@ namespace osu.Game.Overlays } } + // using string literals as there's no proper processing for LocalizeStrings yet public class SupporterRequiredDrawable : CompositeDrawable { - private LinkFlowContainer linkFlowContainer; + private OsuSpriteText supporterRequiredText; public SupporterRequiredDrawable() { RelativeSizeAxes = Axes.X; Height = 225; Alpha = 0; - - linkFlowContainer = linkFlowContainer = new LinkFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding - { - Bottom = 10, - } - }; } [BackgroundDependencyLoader] @@ -304,42 +290,35 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - linkFlowContainer, + supporterRequiredText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Bottom = 10 }, + }, + createSupporterTagLink(), } }); } - public void UpdateSupportRequiredText(string filtersStr) { - string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(filtersStr, "{1}").ToString().Split("{1}"); + public void UpdateText(bool playedFilter, bool rankFilter) { + List filters = new List(); + if (playedFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); + if (rankFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); + supporterRequiredText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); + } - linkFlowContainer.Clear(); - linkFlowContainer.AddText( - text[0], - t => - { - t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.White; - } - ); + public Drawable createSupporterTagLink() { + LinkFlowContainer supporterTagLink = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 10 }, + }; - linkFlowContainer.AddLink( - BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), - "https://osu.ppy.sh/store/products/supporter-tag", - t => - { - t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.FromHex("#A6C8D9"); - } - ); - - linkFlowContainer.AddText( - text[1], - t => - { - t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.White; - } - ); + supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), Online.Chat.LinkAction.External, "https://osu.ppy.sh/store/products/supporter-tag"); + return supporterTagLink; } } From 33674563041102d02722fd98366eab6d3c48aa92 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 14:30:54 +0800 Subject: [PATCH 0900/1621] fixed SupporterRequiredDrawable style --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5ddad1c9fc..42a7253276 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -294,6 +294,8 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 16), + Colour = Colour4.White, Margin = new MarginPadding { Bottom = 10 }, }, createSupporterTagLink(), From b42aedeb8171352bb56ed5334b0a347f43865335 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 14:43:54 +0800 Subject: [PATCH 0901/1621] fixed code style --- osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 9128f72d6e..2146ea333a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -179,7 +179,6 @@ namespace osu.Game.Tests.Visual.Online expectedPlaceholderShown(false, false); } - private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); From 22cc1e14527c14bb2aa16052cdd5bb3ce27072dc Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 15:31:47 +0800 Subject: [PATCH 0902/1621] fixed code style base on code analysis --- osu.Game/Overlays/BeatmapListingOverlay.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 42a7253276..407b737db5 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -280,7 +279,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Children = new Drawable[] + Children = new[] { new Sprite { @@ -303,14 +302,16 @@ namespace osu.Game.Overlays }); } - public void UpdateText(bool playedFilter, bool rankFilter) { + public void UpdateText(bool playedFilter, bool rankFilter) + { List filters = new List(); if (playedFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); if (rankFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); supporterRequiredText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); } - public Drawable createSupporterTagLink() { + private Drawable createSupporterTagLink() + { LinkFlowContainer supporterTagLink = new LinkFlowContainer { Anchor = Anchor.Centre, From ba15f7c19b1c13b59a8eec859a0312efec07aba9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 10:47:38 +0300 Subject: [PATCH 0903/1621] 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 b162da5ee0265726f99b8c847b7b31512cae0e88 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 15:47:47 +0800 Subject: [PATCH 0904/1621] minor code change --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 407b737db5..578e70e630 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -320,7 +320,7 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Bottom = 10 }, }; - supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), Online.Chat.LinkAction.External, "https://osu.ppy.sh/store/products/supporter-tag"); + supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), "https://osu.ppy.sh/store/products/supporter-tag"); return supporterTagLink; } } From e52a58c1bc5f17079ee3fe7fd85b10b2953ff3c0 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 21 Jun 2021 16:24:37 +0800 Subject: [PATCH 0905/1621] Switched to a more reasonable sample sound for now --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index a6476541ab..75089fe237 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -494,7 +494,7 @@ namespace osu.Game.Rulesets.Osu.Mods { InternalChildren = new Drawable[] { - sample = new PausableSkinnableSound(new SampleInfo("spinnerbonus")) // todo: use another sample + sample = new PausableSkinnableSound(new SampleInfo("Gameplay/nightcore-hat")) // todo: use another sample }; } } From 999bf27eae3540067220c61ac64c6c6c7001ab9d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 12:07:00 +0300 Subject: [PATCH 0906/1621] 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 0907/1621] 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 0908/1621] 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 0909/1621] 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 0910/1621] 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 0911/1621] 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 0912/1621] 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 0913/1621] 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 0914/1621] 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 0915/1621] 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 0916/1621] 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 0917/1621] 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 0918/1621] 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 0919/1621] 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 00b4cf1829cd00af1a07c567a25fa9dbda7e0e5a Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 21 Jun 2021 20:20:43 -0700 Subject: [PATCH 0920/1621] Handle sub screen `OnExiting` logic on main screen --- osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index e418d36d40..ceee002c6e 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -240,13 +240,15 @@ namespace osu.Game.Screens.OnlinePlay public override bool OnExiting(IScreen next) { + if (screenStack.CurrentScreen?.OnExiting(next) == true) + return true; + RoomManager.PartRoom(); waves.Hide(); this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); - screenStack.CurrentScreen?.OnExiting(next); base.OnExiting(next); return false; } From 9bcd1e69224d4a7943ba8203765c59b50c5b6ed3 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 21 Jun 2021 20:22:18 -0700 Subject: [PATCH 0921/1621] Move confirm dialog logic to `OnExiting` --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index f9b3549f3c..d7025c2550 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -305,6 +305,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return true; } + return base.OnBackButton(); + } + + public override bool OnExiting(IScreen next) + { + if (client.Room == null) + { + // room has not been created yet; exit immediately. + return base.OnExiting(next); + } + if (!exitConfirmed && dialogOverlay != null) { dialogOverlay.Push(new ConfirmDialog("Are you sure you want to leave this multiplayer match?", () => @@ -316,7 +327,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return true; } - return base.OnBackButton(); + return base.OnExiting(next); } private ModSettingChangeTracker modSettingChangeTracker; From db860980622bfbc29db9c66f70373dd4e24132da Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 21 Jun 2021 20:23:11 -0700 Subject: [PATCH 0922/1621] Fix dialog not closing after spamming OS window close --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index d7025c2550..4b8c4422ec 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -318,11 +318,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!exitConfirmed && dialogOverlay != null) { - dialogOverlay.Push(new ConfirmDialog("Are you sure you want to leave this multiplayer match?", () => + if (dialogOverlay.CurrentDialog is ConfirmDialog confirmDialog) + confirmDialog.PerformOkAction(); + else { - exitConfirmed = true; - this.Exit(); - })); + dialogOverlay.Push(new ConfirmDialog("Are you sure you want to leave this multiplayer match?", () => + { + exitConfirmed = true; + this.Exit(); + })); + } return true; } From 2cdbada87e2bfe8e8bafca51c37242b379243b54 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 21 Jun 2021 20:24:22 -0700 Subject: [PATCH 0923/1621] Fix screen breadcrumb control updating on click --- .../Graphics/UserInterface/ScreenBreadcrumbControl.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs index e85525b2f8..d7bd7d7e01 100644 --- a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; namespace osu.Game.Graphics.UserInterface @@ -19,8 +20,13 @@ namespace osu.Game.Graphics.UserInterface if (stack.CurrentScreen != null) onPushed(null, stack.CurrentScreen); + } - Current.ValueChanged += current => current.NewValue.MakeCurrent(); + protected override void SelectTab(TabItem tab) + { + // override base method to prevent current item from being changed on click. + // depend on screen push/exit to change current item instead. + tab.Value.MakeCurrent(); } private void onPushed(IScreen lastScreen, IScreen newScreen) From f89c154e1810b54b14f48522c1c5c2a6b5bcb546 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 22 Jun 2021 12:24:25 +0700 Subject: [PATCH 0924/1621] 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 0925/1621] 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 0d17fb425973e8935220d06a2f40ff3223b23b86 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Tue, 22 Jun 2021 13:53:21 +0800 Subject: [PATCH 0926/1621] fixed code --- .../Online/TestSceneBeatmapListingOverlay.cs | 194 +++++++++--------- .../BeatmapListingFilterControl.cs | 41 +++- osu.Game/Overlays/BeatmapListingOverlay.cs | 70 +++---- 3 files changed, 170 insertions(+), 135 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 2146ea333a..cd382c2bb2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -24,6 +24,8 @@ namespace osu.Game.Tests.Visual.Online private BeatmapListingOverlay overlay; + private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType().Single(); + [BackgroundDependencyLoader] private void load() { @@ -70,113 +72,123 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupporterOnlyFiltersPlaceholderNoBeatmaps() + public void TestNonSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() { AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, true); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + notFoundPlaceholderShown(); // test non-supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); // test non-supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); - - AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); - - // test supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, true); - - // test supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); - - // test supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); } [Test] - public void TestSupporterOnlyFiltersPlaceholderOneBeatmap() + public void TestSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() + { + AddStep("fetch for 0 beatmaps", () => fetchFor()); + AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); + + // test supporter on Rank Achieved filter + toggleRankFilter(Scoring.ScoreRank.XH); + notFoundPlaceholderShown(); + + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + notFoundPlaceholderShown(); + + // test supporter on Played filter + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + notFoundPlaceholderShown(); + + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); + + // test supporter on both Rank Achieved and Played filter + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + notFoundPlaceholderShown(); + + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); + } + + [Test] + public void TestNonSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() { AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); // test non-supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); // test non-supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); + } + [Test] + public void TestSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() + { + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // test supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(false, false); + toggleRankFilter(Scoring.ScoreRank.XH); + noPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); // test supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + noPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); // test supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + noPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); } private void fetchFor(params BeatmapSetInfo[] beatmaps) @@ -185,44 +197,36 @@ namespace osu.Game.Tests.Visual.Online setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b))); // trigger arbitrary change for fetching. - overlay.ChildrenOfType().Single().Query.TriggerChange(); + searchControl.Query.TriggerChange(); } - private void toggleRandomRankFilter() + private void toggleRankFilter(Scoring.ScoreRank rank) { - short r = TestContext.CurrentContext.Random.NextShort(); - AddStep("toggle Random Rank Achieved filter", () => + AddStep("toggle Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - overlay.ChildrenOfType().Single().Ranks.Add((Scoring.ScoreRank)(r % 8)); + searchControl.Ranks.Clear(); + searchControl.Ranks.Add(rank); }); } - private void toggleRandomSupporterOnlyPlayedFilter() + private void toggleSupporterOnlyPlayedFilter(SearchPlayed played) { - short r = TestContext.CurrentContext.Random.NextShort(); - AddStep("toggle Random Played filter", () => overlay.ChildrenOfType().Single().Played.Value = (SearchPlayed)(r % 2 + 1)); + AddStep("toggle Played filter", () => searchControl.Played.Value = played); } - private void expectedPlaceholderShown(bool supporterRequiredShown, bool notFoundShown) + private void supporterRequiredPlaceholderShown() { - if (supporterRequiredShown) - { - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - } - else - { - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - } + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } - if (notFoundShown) - { - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - } - else - { - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - } + private void notFoundPlaceholderShown() + { + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + + private void noPlaceholderShown() + { + AddUntilStep("no placeholder shown", () => !overlay.ChildrenOfType().Any() && !overlay.ChildrenOfType().Any()); } private class TestAPIBeatmapSet : APIBeatmapSet diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 6e83dc0bf4..f49d913bb2 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -10,11 +10,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; +using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -26,7 +28,7 @@ namespace osu.Game.Overlays.BeatmapListing /// Fired when a search finishes. Contains only new items in the case of pagination. /// Fired with BeatmapListingSearchControl when non-supporter user used supporter-only filters. /// - public Action, BeatmapListingSearchControl> SearchFinished; + public Action SearchFinished; /// /// Fired when search criteria change. @@ -216,11 +218,19 @@ namespace osu.Game.Overlays.BeatmapListing // check if an non-supporter user used supporter-only filters if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) { - SearchFinished?.Invoke(sets, searchControl); + List filters = new List(); + + if (searchControl.Played.Value != SearchPlayed.Any) + filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed); + + if (searchControl.Ranks.Any()) + filters.Add(BeatmapsStrings.ListingSearchFiltersRank); + + SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); } else { - SearchFinished?.Invoke(sets, null); + SearchFinished?.Invoke(SearchResult.ResultsReturned(sets)); } }; @@ -246,5 +256,30 @@ namespace osu.Game.Overlays.BeatmapListing base.Dispose(isDisposing); } + + public enum SearchResultType + { + ResultsReturned, + SupporterOnlyFilter + } + + public struct SearchResult + { + public SearchResultType Type { get; private set; } + public List Results { get; private set; } + public List Filters { get; private set; } + + public static SearchResult ResultsReturned(List results) => new SearchResult + { + Type = SearchResultType.ResultsReturned, + Results = results + }; + + public static SearchResult SupporterOnlyFilter(List filters) => new SearchResult + { + Type = SearchResultType.SupporterOnlyFilter, + Filters = filters + }; + } } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 578e70e630..63800e6585 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Localisation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -119,28 +120,28 @@ namespace osu.Game.Overlays private Task panelLoadDelegate; - private void onSearchFinished(List beatmaps, BeatmapListingSearchControl searchControl) + private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult) { - var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b) + // non-supporter user used supporter-only filters + if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilter) + { + supporterRequiredContent.UpdateText(searchResult.Filters); + addContentToPlaceholder(supporterRequiredContent); + return; + } + + var newPanels = searchResult.Results.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }); - // non-supporter user used supporter-only filters - if (searchControl != null) - { - supporterRequiredContent.UpdateText(searchControl.Played.Value != SearchPlayed.Any, searchControl.Ranks.Any()); - LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); - return; - } - if (filterControl.CurrentPage == 0) { //No matches case if (!newPanels.Any()) { - LoadComponentAsync(notFoundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + addContentToPlaceholder(notFoundContent); return; } @@ -182,16 +183,11 @@ namespace osu.Game.Overlays { var transform = lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent) + if (lastContent == notFoundContent || lastContent == supporterRequiredContent) { - // not found display may be used multiple times, so don't expire/dispose it. + // the placeholder may be used multiple times, so don't expire/dispose it. transform.Schedule(() => panelTarget.Remove(lastContent)); } - else if (lastContent == supporterRequiredContent) - { - // supporter required display may be used multiple times, so don't expire/dispose it. - transform.Schedule(() => panelTarget.Remove(supporterRequiredContent)); - } else { // Consider the case when the new content is smaller than the last content. @@ -260,7 +256,7 @@ namespace osu.Game.Overlays // using string literals as there's no proper processing for LocalizeStrings yet public class SupporterRequiredDrawable : CompositeDrawable { - private OsuSpriteText supporterRequiredText; + private OsuSpriteText filtersText; public SupporterRequiredDrawable() { @@ -289,30 +285,20 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - supporterRequiredText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 16), - Colour = Colour4.White, - Margin = new MarginPadding { Bottom = 10 }, - }, - createSupporterTagLink(), + createSupporterText(), } }); } - public void UpdateText(bool playedFilter, bool rankFilter) + public void UpdateText(List filters) { - List filters = new List(); - if (playedFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); - if (rankFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); - supporterRequiredText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); + // use string literals for now + filtersText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); } - private Drawable createSupporterTagLink() + private Drawable createSupporterText() { - LinkFlowContainer supporterTagLink = new LinkFlowContainer + LinkFlowContainer supporterRequiredText = new LinkFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -320,8 +306,18 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Bottom = 10 }, }; - supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), "https://osu.ppy.sh/store/products/supporter-tag"); - return supporterTagLink; + filtersText = (OsuSpriteText)supporterRequiredText.AddText( + "_", + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.White; + } + ).First(); + + supporterRequiredText.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), @"/store/products/supporter-tag"); + + return supporterRequiredText; } } From e9339d6100b43c72308862582215c00ff3a2950d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 15:16:19 +0900 Subject: [PATCH 0927/1621] 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 0928/1621] 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 3745101f326b2d677f2c5ff124f01a43269c518e Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 10 Jun 2021 15:31:20 +0800 Subject: [PATCH 0929/1621] Extract seed setting control to IHasSeed --- osu.Game/Rulesets/Mods/IHasSeed.cs | 94 +++++++++++++++++++++++++++++ osu.Game/Rulesets/Mods/ModRandom.cs | 82 +------------------------ 2 files changed, 96 insertions(+), 80 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IHasSeed.cs diff --git a/osu.Game/Rulesets/Mods/IHasSeed.cs b/osu.Game/Rulesets/Mods/IHasSeed.cs new file mode 100644 index 0000000000..b6852d960b --- /dev/null +++ b/osu.Game/Rulesets/Mods/IHasSeed.cs @@ -0,0 +1,94 @@ +// 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.UserInterface; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Rulesets.Mods +{ + public interface IHasSeed + { + public Bindable Seed { get; } + } + + public class SeedSettingsControl : SettingsItem + { + protected override Drawable CreateControl() => new SeedControl + { + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 5 } + }; + + private sealed class SeedControl : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current; + set + { + current.Current = value; + seedNumberBox.Text = value.Value.ToString(); + } + } + + private readonly OsuNumberBox seedNumberBox; + + public SeedControl() + { + AutoSizeAxes = Axes.Y; + + InternalChildren = new[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(GridSizeMode.Relative, 0.25f) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + seedNumberBox = new OsuNumberBox + { + RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true + } + } + } + } + }; + + seedNumberBox.Current.BindValueChanged(e => + { + int? value = null; + + if (int.TryParse(e.NewValue, out var intVal)) + value = intVal; + + current.Value = value; + }); + } + + protected override void Update() + { + if (current.Value == null) + seedNumberBox.Text = current.Current.Value.ToString(); + } + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index e0c3008ae8..c6040a48aa 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -8,11 +8,10 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics; -using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods { - public abstract class ModRandom : Mod + public abstract class ModRandom : Mod, IHasSeed { public override string Name => "Random"; public override string Acronym => "RD"; @@ -20,88 +19,11 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.Dice; public override double ScoreMultiplier => 1; - [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(ModRandomSettingsControl))] + [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SeedSettingsControl))] public Bindable Seed { get; } = new Bindable { Default = null, Value = null }; - - private class ModRandomSettingsControl : SettingsItem - { - protected override Drawable CreateControl() => new SeedControl - { - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 5 } - }; - - private sealed class SeedControl : CompositeDrawable, IHasCurrentValue - { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current - { - get => current; - set - { - current.Current = value; - seedNumberBox.Text = value.Value.ToString(); - } - } - - private readonly SettingsNumberBox.NumberBox seedNumberBox; - - public SeedControl() - { - AutoSizeAxes = Axes.Y; - - InternalChildren = new[] - { - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), - new Dimension(GridSizeMode.Relative, 0.25f) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - seedNumberBox = new SettingsNumberBox.NumberBox - { - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true - } - } - } - } - }; - - seedNumberBox.Current.BindValueChanged(e => - { - int? value = null; - - if (int.TryParse(e.NewValue, out var intVal)) - value = intVal; - - current.Value = value; - }); - } - - protected override void Update() - { - if (current.Value == null) - seedNumberBox.Text = current.Current.Value.ToString(); - } - } - } } } From fc224c53f4ee203ae3ad6b9f7d556c24b8e892f2 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 22 Jun 2021 14:49:37 +0800 Subject: [PATCH 0930/1621] Remove extra usings --- osu.Game/Rulesets/Mods/ModRandom.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index c6040a48aa..61297c162d 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -2,10 +2,7 @@ // 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.Sprites; -using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics; From 0ad189e357f93d023dbaec929713ad40e867df36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 16:19:55 +0900 Subject: [PATCH 0931/1621] 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 0932/1621] 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 0933/1621] 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 0934/1621] 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 0935/1621] 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 0936/1621] 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 0937/1621] 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 a9c783025262cf069e39c92f02e6760cc6c3d8cb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 10:05:03 +0900 Subject: [PATCH 0938/1621] Fix NRE when hit object blueprint is not implemented --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 3e97e15cca..3552305664 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -256,7 +256,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (drawable == null) return null; - return CreateHitObjectBlueprintFor(item).With(b => b.DrawableObject = drawable); + return CreateHitObjectBlueprintFor(item)?.With(b => b.DrawableObject = drawable); } public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null; From fbe44dac34f255e4ac297fceedcd4c55ca8e1afb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 10:05:29 +0900 Subject: [PATCH 0939/1621] Add empty catch hit object composer --- .../Editor/TestSceneEditor.cs | 14 +++++++++++++ osu.Game.Rulesets.Catch/CatchRuleset.cs | 4 ++++ .../Edit/CatchHitObjectComposer.cs | 20 +++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs new file mode 100644 index 0000000000..161c685043 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs @@ -0,0 +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 NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + [TestFixture] + public class TestSceneEditor : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new CatchRuleset(); + } +} diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 23ce444560..cdd9a24f6a 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -22,7 +22,9 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using System; using osu.Framework.Extensions.EnumExtensions; +using osu.Game.Rulesets.Catch.Edit; using osu.Game.Rulesets.Catch.Skinning.Legacy; +using osu.Game.Rulesets.Edit; using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch @@ -182,5 +184,7 @@ namespace osu.Game.Rulesets.Catch public int LegacyID => 2; public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); + + public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this); } } diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs new file mode 100644 index 0000000000..392d7f004f --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.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 System.Collections.Generic; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class CatchHitObjectComposer : HitObjectComposer + { + public CatchHitObjectComposer(CatchRuleset ruleset) + : base(ruleset) + { + } + + protected override IReadOnlyList CompositionTools => System.Array.Empty(); + } +} From b8ccfe6ea7d4dfde1c743ae814090060148cf9cd Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 10:39:32 +0900 Subject: [PATCH 0940/1621] Add basic selection blueprint movement logic --- .../BananaShowerSelectionBlueprint.cs | 15 +++++++ .../Blueprints/CatchSelectionBlueprint.cs | 38 +++++++++++++++++ .../Blueprints/FruitSelectionBlueprint.cs | 15 +++++++ .../Edit/CatchBlueprintContainer.cs | 35 ++++++++++++++++ .../Edit/CatchHitObjectComposer.cs | 12 ++++++ .../Edit/CatchSelectionHandler.cs | 41 +++++++++++++++++++ 6 files changed, 156 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerSelectionBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerSelectionBlueprint.cs new file mode 100644 index 0000000000..9132b1a9e8 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerSelectionBlueprint.cs @@ -0,0 +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 osu.Game.Rulesets.Catch.Objects; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public class BananaShowerSelectionBlueprint : CatchSelectionBlueprint + { + public BananaShowerSelectionBlueprint(BananaShower hitObject) + : base(hitObject) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs new file mode 100644 index 0000000000..3ca57590e2 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs @@ -0,0 +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 osu.Framework.Allocation; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public abstract class CatchSelectionBlueprint : HitObjectSelectionBlueprint + where THitObject : CatchHitObject + { + [Resolved] + private Playfield playfield { get; set; } + + protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; + + public override Vector2 ScreenSpaceSelectionPoint + { + get + { + float x = HitObject.OriginalX; + float y = HitObjectContainer.PositionAtTime(HitObject.StartTime); + return HitObjectContainer.ToScreenSpace(new Vector2(x, y + HitObjectContainer.DrawHeight)); + } + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SelectionQuad.Contains(screenSpacePos); + + protected CatchSelectionBlueprint(THitObject hitObject) + : base(hitObject) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs new file mode 100644 index 0000000000..f104dbfb63 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs @@ -0,0 +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 osu.Game.Rulesets.Catch.Objects; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public class FruitSelectionBlueprint : CatchSelectionBlueprint + { + public FruitSelectionBlueprint(Fruit hitObject) + : base(hitObject) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs new file mode 100644 index 0000000000..b189a43915 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class CatchBlueprintContainer : ComposeBlueprintContainer + { + public CatchBlueprintContainer(CatchHitObjectComposer composer) + : base(composer) + { + } + + protected override SelectionHandler CreateSelectionHandler() => new CatchSelectionHandler(); + + public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) + { + switch (hitObject) + { + case Fruit fruit: + return new FruitSelectionBlueprint(fruit); + + case BananaShower bananaShower: + return new BananaShowerSelectionBlueprint(bananaShower); + } + + return base.CreateHitObjectBlueprintFor(hitObject); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 392d7f004f..00a10b86f4 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; namespace osu.Game.Rulesets.Catch.Edit { @@ -16,5 +18,15 @@ namespace osu.Game.Rulesets.Catch.Edit } protected override IReadOnlyList CompositionTools => System.Array.Empty(); + + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + { + var result = base.SnapScreenSpacePositionToValidTime(screenSpacePosition); + // TODO: implement position snap + result.ScreenSpacePosition.X = screenSpacePosition.X; + return result; + } + + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this); } } diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs new file mode 100644 index 0000000000..1fe303dc39 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -0,0 +1,41 @@ +// 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.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class CatchSelectionHandler : EditorSelectionHandler + { + [Resolved] + private Playfield playfield { get; set; } + + protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; + + public override bool HandleMovement(MoveSelectionEvent moveEvent) + { + var blueprint = moveEvent.Blueprint; + Vector2 originalPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint); + Vector2 targetPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint + moveEvent.ScreenSpaceDelta); + float deltaX = targetPosition.X - originalPosition.X; + + EditorBeatmap.PerformOnSelection(h => + { + if (!(h is CatchHitObject hitObject)) return; + + if (hitObject is BananaShower) return; + + // TODO: confine in bounds + hitObject.OriginalXBindable.Value += deltaX; + }); + + return true; + } + } +} From c28cd5dd75bfa68ff12762bed9985793f81b6e8c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 11:39:04 +0900 Subject: [PATCH 0941/1621] Add basic juice stream selection blueprint --- .../JuiceStreamSelectionBlueprint.cs | 57 +++++++++++++++++++ .../Edit/CatchBlueprintContainer.cs | 3 + .../Edit/CatchSelectionHandler.cs | 4 ++ 3 files changed, 64 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs new file mode 100644 index 0000000000..0cbce144a3 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -0,0 +1,57 @@ +// 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.Graphics.Primitives; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public class JuiceStreamSelectionBlueprint : CatchSelectionBlueprint + { + public override Quad SelectionQuad => HitObjectContainer.ToScreenSpace(computeBoundingBox().Offset(new Vector2(0, HitObjectContainer.DrawHeight))); + + private float minNestedX; + private float maxNestedX; + + public JuiceStreamSelectionBlueprint(JuiceStream hitObject) + : base(hitObject) + { + } + + [BackgroundDependencyLoader] + private void load() + { + HitObject.DefaultsApplied += onDefaultsApplied; + calculateObjectBounds(); + } + + private void onDefaultsApplied(HitObject _) => calculateObjectBounds(); + + private void calculateObjectBounds() + { + minNestedX = HitObject.NestedHitObjects.OfType().Min(nested => nested.OriginalX) - HitObject.OriginalX; + maxNestedX = HitObject.NestedHitObjects.OfType().Max(nested => nested.OriginalX) - HitObject.OriginalX; + } + + private RectangleF computeBoundingBox() + { + float left = HitObject.OriginalX + minNestedX; + float right = HitObject.OriginalX + maxNestedX; + float top = HitObjectContainer.PositionAtTime(HitObject.EndTime); + float bottom = HitObjectContainer.PositionAtTime(HitObject.StartTime); + float objectRadius = CatchHitObject.OBJECT_RADIUS * HitObject.Scale; + return new RectangleF(left, top, right - left, bottom - top).Inflate(objectRadius); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + HitObject.DefaultsApplied -= onDefaultsApplied; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs index b189a43915..7f2782a474 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs @@ -25,6 +25,9 @@ namespace osu.Game.Rulesets.Catch.Edit case Fruit fruit: return new FruitSelectionBlueprint(fruit); + case JuiceStream juiceStream: + return new JuiceStreamSelectionBlueprint(juiceStream); + case BananaShower bananaShower: return new BananaShowerSelectionBlueprint(bananaShower); } diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 1fe303dc39..a0cedcb807 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.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.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects; @@ -33,6 +34,9 @@ namespace osu.Game.Rulesets.Catch.Edit // TODO: confine in bounds hitObject.OriginalXBindable.Value += deltaX; + + foreach (var nested in hitObject.NestedHitObjects.OfType()) + nested.OriginalXBindable.Value += deltaX; }); return true; From 0078d7dc18af34d5216f7acf98b2fb55aa6d6256 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 12:04:24 +0900 Subject: [PATCH 0942/1621] Add outline to selected fruit --- .../Blueprints/Components/FruitOutline.cs | 39 +++++++++++++++++++ .../Blueprints/FruitSelectionBlueprint.cs | 12 ++++++ 2 files changed, 51 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs new file mode 100644 index 0000000000..35c481d793 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Skinning.Default; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components +{ + public class FruitOutline : CompositeDrawable + { + public FruitOutline() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.Centre; + Size = new Vector2(2 * CatchHitObject.OBJECT_RADIUS); + // TODO: use skinned component? + InternalChild = new BorderPiece(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour osuColour) + { + Colour = osuColour.Yellow; + } + + public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject) + { + X = hitObject.EffectiveX; + Y = hitObjectContainer.PositionAtTime(hitObject.StartTime); + Scale = new Vector2(hitObject.Scale); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs index f104dbfb63..9665aac2fb 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs @@ -1,15 +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.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; namespace osu.Game.Rulesets.Catch.Edit.Blueprints { public class FruitSelectionBlueprint : CatchSelectionBlueprint { + private readonly FruitOutline outline; + public FruitSelectionBlueprint(Fruit hitObject) : base(hitObject) { + InternalChild = outline = new FruitOutline(); + } + + protected override void Update() + { + base.Update(); + + if (IsSelected) + outline.UpdateFrom(HitObjectContainer, HitObject); } } } From 4d7a8777954d0b821688a9b037773740dfc761b1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 12:10:16 +0900 Subject: [PATCH 0943/1621] Add basic fruit placement tool --- .../Blueprints/CatchPlacementBlueprint.cs | 27 ++++++++++ .../Blueprints/FruitPlacementBlueprint.cs | 50 +++++++++++++++++++ .../Edit/CatchHitObjectComposer.cs | 5 +- .../Edit/FruitCompositionTool.cs | 20 ++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs new file mode 100644 index 0000000000..69054e2c81 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.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.Allocation; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public class CatchPlacementBlueprint : PlacementBlueprint + where THitObject : CatchHitObject, new() + { + protected new THitObject HitObject => (THitObject)base.HitObject; + + protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; + + [Resolved] + private Playfield playfield { get; set; } + + public CatchPlacementBlueprint() + : base(new THitObject()) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs new file mode 100644 index 0000000000..0f28cf6786 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs @@ -0,0 +1,50 @@ +// 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.Input.Events; +using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osuTK.Input; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public class FruitPlacementBlueprint : CatchPlacementBlueprint + { + private readonly FruitOutline outline; + + public FruitPlacementBlueprint() + { + InternalChild = outline = new FruitOutline(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + BeginPlacement(); + } + + protected override void Update() + { + base.Update(); + + outline.UpdateFrom(HitObjectContainer, HitObject); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button != MouseButton.Left) return base.OnMouseDown(e); + + EndPlacement(true); + return true; + } + + public override void UpdateTimeAndPosition(SnapResult result) + { + base.UpdateTimeAndPosition(result); + + HitObject.X = ToLocalSpace(result.ScreenSpacePosition).X; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 00a10b86f4..2099be8864 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -17,7 +17,10 @@ namespace osu.Game.Rulesets.Catch.Edit { } - protected override IReadOnlyList CompositionTools => System.Array.Empty(); + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + { + new FruitCompositionTool(), + }; public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { diff --git a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs new file mode 100644 index 0000000000..da716bbe1d --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.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.Rulesets.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class FruitCompositionTool : HitObjectCompositionTool + { + public FruitCompositionTool() + : base(nameof(Fruit)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint(); + } +} From e8907b53a8678b52657ea75b46a2fcea017e0ecb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 12:30:09 +0900 Subject: [PATCH 0944/1621] Add basic banana shower placement tool --- .../Edit/BananaShowerCompositionTool.cs | 20 ++++++ .../BananaShowerPlacementBlueprint.cs | 72 +++++++++++++++++++ .../Blueprints/Components/TimeSpanOutline.cs | 61 ++++++++++++++++ .../Edit/CatchHitObjectComposer.cs | 1 + 4 files changed, 154 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs diff --git a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs new file mode 100644 index 0000000000..be18f223eb --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.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.Rulesets.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class BananaShowerCompositionTool : HitObjectCompositionTool + { + public BananaShowerCompositionTool() + : base(nameof(BananaShower)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs new file mode 100644 index 0000000000..843cca3c7b --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs @@ -0,0 +1,72 @@ +// 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.Input.Events; +using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osuTK.Input; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints +{ + public class BananaShowerPlacementBlueprint : CatchPlacementBlueprint + { + private readonly TimeSpanOutline outline; + + public BananaShowerPlacementBlueprint() + { + InternalChild = outline = new TimeSpanOutline(); + } + + protected override void Update() + { + base.Update(); + + outline.UpdateFrom(HitObjectContainer, HitObject); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + switch (PlacementActive) + { + case PlacementState.Waiting: + if (e.Button != MouseButton.Left) break; + + BeginPlacement(true); + return true; + + case PlacementState.Active: + if (e.Button != MouseButton.Right) break; + + if (HitObject.Duration < 0) + { + HitObject.StartTime = HitObject.EndTime; + HitObject.Duration = -HitObject.Duration; + } + + EndPlacement(HitObject.Duration > 0); + return true; + } + + return base.OnMouseDown(e); + } + + public override void UpdateTimeAndPosition(SnapResult result) + { + base.UpdateTimeAndPosition(result); + + if (!(result.Time is double time)) return; + + switch (PlacementActive) + { + case PlacementState.Waiting: + HitObject.StartTime = time; + break; + + case PlacementState.Active: + HitObject.EndTime = time; + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs new file mode 100644 index 0000000000..4e5164e965 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components +{ + public class TimeSpanOutline : CompositeDrawable + { + private const float border_width = 4; + + private bool isEmpty = true; + + public TimeSpanOutline() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + RelativeSizeAxes = Axes.X; + + Masking = true; + BorderThickness = border_width; + + // a box is needed to make edge visible + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Transparent + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour osuColour) + { + BorderColour = osuColour.Yellow; + } + + public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, BananaShower hitObject) + { + float startY = hitObjectContainer.PositionAtTime(hitObject.StartTime); + float endY = hitObjectContainer.PositionAtTime(hitObject.EndTime); + + Y = Math.Max(startY, endY); + float height = Math.Abs(startY - endY); + + bool wasEmpty = isEmpty; + isEmpty = height == 0; + if (wasEmpty != isEmpty) + this.FadeTo(isEmpty ? 0.5f : 1f, 150); + + Height = Math.Max(height, border_width); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 2099be8864..c11e5eb45b 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Catch.Edit protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { new FruitCompositionTool(), + new BananaShowerCompositionTool() }; public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) From 21331d3a13c80c4f8fc4c3b8aac4438fec1ea7ae Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 12:47:24 +0900 Subject: [PATCH 0945/1621] Disable caught object stacking in editor --- .../Edit/CatchEditorPlayfield.cs | 27 +++++++++++++++++++ .../Edit/CatchHitObjectComposer.cs | 6 +++++ .../Edit/DrawableCatchEditorRuleset.cs | 21 +++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs diff --git a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs new file mode 100644 index 0000000000..d383eb9ba6 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.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.Game.Beatmaps; +using osu.Game.Rulesets.Catch.UI; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class CatchEditorPlayfield : CatchPlayfield + { + // TODO fixme: the size of the catcher is not changed when circle size is changed in setup screen. + public CatchEditorPlayfield(BeatmapDifficulty difficulty) + : base(difficulty) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // TODO: honor "hit animation" setting? + CatcherArea.MovableCatcher.CatchFruitOnPlate = false; + + // TODO: disable hit lighting as well + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index c11e5eb45b..d9712bc8e9 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -2,9 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -17,6 +20,9 @@ namespace osu.Game.Rulesets.Catch.Edit { } + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) => + new DrawableCatchEditorRuleset(ruleset, beatmap, mods); + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { new FruitCompositionTool(), diff --git a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs new file mode 100644 index 0000000000..0344709d45 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs @@ -0,0 +1,21 @@ +// 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.Catch.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class DrawableCatchEditorRuleset : DrawableCatchRuleset + { + public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + : base(ruleset, beatmap, mods) + { + } + + protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.BeatmapInfo.BaseDifficulty); + } +} From c4fde635c6f44c18687475a0f76401d9d66a35e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 17:41:20 +0900 Subject: [PATCH 0946/1621] 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 0947/1621] 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 0948/1621] 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 804a0433ccc40ed49e71b873b863a5507ee4dbaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 17:59:55 +0900 Subject: [PATCH 0949/1621] 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 0950/1621] 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 0951/1621] 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 0952/1621] 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 a4b66bec2e570af80da16610bc99b859fa4651bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 18:18:25 +0900 Subject: [PATCH 0953/1621] Ensure realm contexts are flushed when update thread changes native thread --- osu.Game/OsuGameBase.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3a08ef684f..fb083ea7d5 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -182,6 +182,13 @@ namespace osu.Game dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage)); dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); + + Host.UpdateThreadChanging += () => + { + var blocking = realmFactory.BlockAllOperations(); + Schedule(() => blocking.Dispose()); + }; + AddInternal(realmFactory); dependencies.CacheAs(Storage); From 37f7486fb1f2be6c5c7a2cf355266ec811265f77 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 12:25:29 +0300 Subject: [PATCH 0954/1621] 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() { From b9a9174168f62115acb2bcc78f9054d6a248c27e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 18:26:42 +0900 Subject: [PATCH 0955/1621] Remove live realm bindings for now --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 37 +++++++++------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 432c52c2e9..d2e07c7d15 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -159,28 +159,6 @@ namespace osu.Game.Overlays.Toolbar }; } - private RealmKeyBinding realmKeyBinding; - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (Hotkey == null) return; - - 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.KeyCombinationString)) - updateKeyBindingTooltip(); - }; - } - - updateKeyBindingTooltip(); - } - protected override bool OnMouseDown(MouseDownEvent e) => true; protected override bool OnClick(ClickEvent e) @@ -196,6 +174,7 @@ namespace osu.Game.Overlays.Toolbar HoverBackground.FadeIn(200); tooltipContainer.FadeIn(100); + return base.OnHover(e); } @@ -222,6 +201,20 @@ namespace osu.Game.Overlays.Toolbar private void updateKeyBindingTooltip() { + if (Hotkey == null) return; + + var realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.ActionInt == (int)Hotkey.Value); + + // TODO: temporarily disabled to avoid crashes when querying after ExecutionState is changed. + // if (realmKeyBinding != null) + // { + // realmKeyBinding.PropertyChanged += (sender, args) => + // { + // if (args.PropertyName == nameof(realmKeyBinding.KeyCombinationString)) + // updateKeyBindingTooltip(); + // }; + // } + if (realmKeyBinding != null) { var keyBindingString = realmKeyBinding.KeyCombination.ReadableString(); From 2b1f461d79f8999dea041d4d10c2dd45543b1c62 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 21 Jun 2021 12:24:57 +0300 Subject: [PATCH 0956/1621] Pass empty resource store for `FallbackShaderManager` base constructor --- osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index b910f708a2..e49e515d2e 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -189,6 +189,7 @@ namespace osu.Game.Rulesets.UI private readonly ShaderManager fallback; public FallbackShaderManager(ShaderManager primary, ShaderManager fallback) + : base(new ResourceStore()) { this.primary = primary; this.fallback = fallback; From bea828a36433c855f1765c5686848a0f44597adf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 13:17:34 +0300 Subject: [PATCH 0957/1621] Also pass empty resource in `TestSceneDrawableRulesetDependencies` --- .../Rulesets/TestSceneDrawableRulesetDependencies.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index 94cf317c20..c357fccd27 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.UI; @@ -144,6 +145,11 @@ namespace osu.Game.Tests.Rulesets private class TestShaderManager : ShaderManager { + public TestShaderManager() + : base(new ResourceStore()) + { + } + public override byte[] LoadRaw(string name) => null; public bool IsDisposed { get; private set; } From f03c2bab481df4a343171b34e9eb1303095cee47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Jun 2021 22:45:13 +0900 Subject: [PATCH 0958/1621] Update event name in line with framework changes --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index fb083ea7d5..43c81783fe 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -183,7 +183,7 @@ namespace osu.Game dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); - Host.UpdateThreadChanging += () => + Host.UpdateThreadPausing += () => { var blocking = realmFactory.BlockAllOperations(); Schedule(() => blocking.Dispose()); From ee84364d7ca7eea57d37257c198694ec08d6fb80 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Jun 2021 20:38:24 +0300 Subject: [PATCH 0959/1621] Resolve conflict issues --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 4a4773fce4..e46ba6d857 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -51,7 +51,6 @@ namespace osu.Game.Skinning private AudioManager audio { get; set; } [Resolved] - private SkinManager skinManager { get; set; } private ISkinSource skinSource { get; set; } [BackgroundDependencyLoader] @@ -84,6 +83,8 @@ namespace osu.Game.Skinning break; } } + + SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) @@ -102,9 +103,6 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, Beatmap)); - - SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); if (skinSource != null) skinSource.SourceChanged -= OnSourceChanged; } From 9cb9ef5c563316df57f7a49400359463ead3f49b Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Tue, 22 Jun 2021 13:31:41 -0700 Subject: [PATCH 0960/1621] Refactor the menu's max height to be a property --- osu.Game/Overlays/Settings/SettingsEnumDropdown.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index 9987a0c607..fa9973fb08 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -14,13 +14,15 @@ namespace osu.Game.Overlays.Settings protected new class DropdownControl : OsuEnumDropdown { + protected virtual int MenuMaxHeight => 200; + public DropdownControl() { Margin = new MarginPadding { Top = 5 }; RelativeSizeAxes = Axes.X; } - protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200); + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = MenuMaxHeight); } } } From 59928a7d91d230e32fec56d395d65c485457cc52 Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Tue, 22 Jun 2021 13:32:45 -0700 Subject: [PATCH 0961/1621] Decrease the max height of dropdown menus in mod settings --- osu.Game/Configuration/SettingSourceAttribute.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 3e50613093..cd0652f401 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -12,6 +12,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Configuration @@ -149,7 +150,7 @@ namespace osu.Game.Configuration break; case IBindable bindable: - var dropdownType = typeof(SettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); + var dropdownType = typeof(ModSettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); var dropdown = (Drawable)Activator.CreateInstance(dropdownType); dropdownType.GetProperty(nameof(SettingsDropdown.LabelText))?.SetValue(dropdown, attr.Label); @@ -183,5 +184,16 @@ namespace osu.Game.Configuration => obj.GetSettingsSourceProperties() .OrderBy(attr => attr.Item1) .ToArray(); + + private class ModSettingsEnumDropdown : SettingsEnumDropdown + where T : struct, Enum + { + protected override OsuDropdown CreateDropdown() => new ModDropdownControl(); + + private class ModDropdownControl : DropdownControl + { + protected override int MenuMaxHeight => 100; + } + } } } From 1a7bfafc698f03a840d8e8381281f0aeb1b39df0 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 09:34:11 +0900 Subject: [PATCH 0962/1621] Add icon for composition tools --- osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs | 4 ++++ osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs index be18f223eb..31075db7d1 100644 --- a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs +++ b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs @@ -1,6 +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 osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Edit.Blueprints; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Catch.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); + public override PlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs index da716bbe1d..f776fe39c1 100644 --- a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs +++ b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs @@ -1,6 +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 osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Edit.Blueprints; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Catch.Edit { } + public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); + public override PlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint(); } } From e96814bb866cb631bf5a8111df88f99c686b199e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 09:37:30 +0900 Subject: [PATCH 0963/1621] Remove comment about using skin for blueprint As the current game-wise direction is not using skin elements in blueprints. The design of the blueprint could be improved somehow, though. --- .../Edit/Blueprints/Components/FruitOutline.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs index 35c481d793..8769acc382 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs @@ -19,7 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components Anchor = Anchor.BottomLeft; Origin = Anchor.Centre; Size = new Vector2(2 * CatchHitObject.OBJECT_RADIUS); - // TODO: use skinned component? InternalChild = new BorderPiece(); } From eec44574739ed2348b620b141b8b61a94568920d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 09:40:07 +0900 Subject: [PATCH 0964/1621] Add `[CanBeNull]` to methods returning null by default --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 ++ .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 8a4d381535..185f029d14 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -94,6 +95,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Creates a for a specific item. /// /// The item to create the overlay for. + [CanBeNull] protected virtual SelectionBlueprint CreateBlueprintFor(T item) => null; protected virtual DragBox CreateDragBox(Action performSelect) => new DragBox(performSelect); diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 3552305664..79b38861ee 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using Humanizer; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -259,6 +260,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return CreateHitObjectBlueprintFor(item)?.With(b => b.DrawableObject = drawable); } + [CanBeNull] public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null; private void hitObjectAdded(HitObject obj) From a9b8736f70b19173731153284b5a29347870ee34 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 10:18:44 +0900 Subject: [PATCH 0965/1621] Order field and properties consistently --- .../Edit/Blueprints/CatchSelectionBlueprint.cs | 10 +++++----- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs index 3ca57590e2..298f9474b0 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs @@ -13,11 +13,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints public abstract class CatchSelectionBlueprint : HitObjectSelectionBlueprint where THitObject : CatchHitObject { - [Resolved] - private Playfield playfield { get; set; } - - protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; - public override Vector2 ScreenSpaceSelectionPoint { get @@ -30,6 +25,11 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SelectionQuad.Contains(screenSpacePos); + protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; + + [Resolved] + private Playfield playfield { get; set; } + protected CatchSelectionBlueprint(THitObject hitObject) : base(hitObject) { diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index a0cedcb807..f3a4d72c87 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -14,11 +14,11 @@ namespace osu.Game.Rulesets.Catch.Edit { public class CatchSelectionHandler : EditorSelectionHandler { + protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; + [Resolved] private Playfield playfield { get; set; } - protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer; - public override bool HandleMovement(MoveSelectionEvent moveEvent) { var blueprint = moveEvent.Blueprint; From 69c8865a043d51dc824b33997f59051b4b82cb72 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 10:19:09 +0900 Subject: [PATCH 0966/1621] Use more consistent method names --- .../Edit/Blueprints/JuiceStreamSelectionBlueprint.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index 0cbce144a3..d6b8c35a09 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { public class JuiceStreamSelectionBlueprint : CatchSelectionBlueprint { - public override Quad SelectionQuad => HitObjectContainer.ToScreenSpace(computeBoundingBox().Offset(new Vector2(0, HitObjectContainer.DrawHeight))); + public override Quad SelectionQuad => HitObjectContainer.ToScreenSpace(getBoundingBox().Offset(new Vector2(0, HitObjectContainer.DrawHeight))); private float minNestedX; private float maxNestedX; @@ -26,18 +26,18 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints private void load() { HitObject.DefaultsApplied += onDefaultsApplied; - calculateObjectBounds(); + computeObjectBounds(); } - private void onDefaultsApplied(HitObject _) => calculateObjectBounds(); + private void onDefaultsApplied(HitObject _) => computeObjectBounds(); - private void calculateObjectBounds() + private void computeObjectBounds() { minNestedX = HitObject.NestedHitObjects.OfType().Min(nested => nested.OriginalX) - HitObject.OriginalX; maxNestedX = HitObject.NestedHitObjects.OfType().Max(nested => nested.OriginalX) - HitObject.OriginalX; } - private RectangleF computeBoundingBox() + private RectangleF getBoundingBox() { float left = HitObject.OriginalX + minNestedX; float right = HitObject.OriginalX + maxNestedX; From 5a5cb39c9f1d6d11f3eec86e03dbf85af8c694c7 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 10:19:39 +0900 Subject: [PATCH 0967/1621] Add some comments about logic --- .../Edit/Blueprints/BananaShowerPlacementBlueprint.cs | 1 + .../Edit/Blueprints/Components/TimeSpanOutline.cs | 5 ++--- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs index 843cca3c7b..6dea8b0712 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints case PlacementState.Active: if (e.Button != MouseButton.Right) break; + // If the duration is negative, swap the start and the end time to make the duration positive. if (HitObject.Duration < 0) { HitObject.StartTime = HitObject.EndTime; diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs index 4e5164e965..f925783919 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs @@ -21,14 +21,13 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components public TimeSpanOutline() { - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; + Anchor = Origin = Anchor.BottomLeft; RelativeSizeAxes = Axes.X; Masking = true; BorderThickness = border_width; - // a box is needed to make edge visible + // A box is needed to make the border visible. InternalChild = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index f3a4d72c87..c1a491d1ce 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.Catch.Edit // TODO: confine in bounds hitObject.OriginalXBindable.Value += deltaX; + // Move the nested hit objects to give an instant result before nested objects are recreated. foreach (var nested in hitObject.NestedHitObjects.OfType()) nested.OriginalXBindable.Value += deltaX; }); From 125e1434017835cb992cd9bcad8b6b580b818cd9 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 10:26:01 +0900 Subject: [PATCH 0968/1621] Fix banana shower placement outline initial opacity --- .../Edit/Blueprints/Components/TimeSpanOutline.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs index f925783919..65dfce0493 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { private const float border_width = 4; + private const float opacity_when_empty = 0.5f; + private bool isEmpty = true; public TimeSpanOutline() @@ -26,6 +28,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components Masking = true; BorderThickness = border_width; + Alpha = opacity_when_empty; // A box is needed to make the border visible. InternalChild = new Box @@ -52,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components bool wasEmpty = isEmpty; isEmpty = height == 0; if (wasEmpty != isEmpty) - this.FadeTo(isEmpty ? 0.5f : 1f, 150); + this.FadeTo(isEmpty ? opacity_when_empty : 1f, 150); Height = Math.Max(height, border_width); } From 0b351c99228cad3d84274a15774415a0a1ddaf87 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 10:57:04 +0900 Subject: [PATCH 0969/1621] Fix "possible NRE" inspection --- .../Compose/Components/Timeline/TimelineBlueprintContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 6f04f36b83..a642768574 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -89,7 +90,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } else { - placementBlueprint = CreateBlueprintFor(obj.NewValue); + placementBlueprint = CreateBlueprintFor(obj.NewValue).AsNonNull(); placementBlueprint.Colour = Color4.MediumPurple; From 49000b9501d9a8818679aede641c4ed843683f88 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 22 Jun 2021 08:48:41 -0700 Subject: [PATCH 0970/1621] Add multiplayer leave navigation tests --- .../Multiplayer/TestSceneMultiplayer.cs | 49 +++++++++++++++++++ osu.Game/Tests/Visual/ScreenTestScene.cs | 8 ++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index c5a6723508..599dfb082b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -6,15 +6,20 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Tests.Resources; @@ -159,6 +164,50 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("play started", () => !multiplayerScreen.IsCurrentScreen()); } + [Test] + public void TestLeaveNavigation() + { + loadMultiplayer(); + + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + AllowedMods = { new OsuModHidden() } + } + } + }); + + AddStep("open mod overlay", () => this.ChildrenOfType().ElementAt(2).Click()); + + AddStep("invoke on back button", () => multiplayerScreen.OnBackButton()); + + AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + + AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden); + + testLeave("lounge tab item", () => this.ChildrenOfType.BreadcrumbTabItem>().First().Click()); + + testLeave("back button", () => multiplayerScreen.OnBackButton()); + + // mimics home button and OS window close + testLeave("forced exit", () => multiplayerScreen.Exit()); + + void testLeave(string actionName, Action action) + { + AddStep($"leave via {actionName}", action); + + AddAssert("dialog overlay is visible", () => DialogOverlay.State.Value == Visibility.Visible); + + AddStep("close dialog overlay", () => InputManager.Key(Key.Escape)); + } + } + private void createRoom(Func room) { AddStep("open room", () => diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index 33cc00e748..b30be05ac4 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Overlays; using osu.Game.Screens; namespace osu.Game.Tests.Visual @@ -19,12 +21,16 @@ namespace osu.Game.Tests.Visual protected override Container Content => content; + [Cached] + protected DialogOverlay DialogOverlay { get; private set; } + protected ScreenTestScene() { base.Content.AddRange(new Drawable[] { Stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, - content = new Container { RelativeSizeAxes = Axes.Both } + content = new Container { RelativeSizeAxes = Axes.Both }, + DialogOverlay = new DialogOverlay() }); } From dc428da06c70797d92563caa3b11e0a89088d74b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 22 Jun 2021 19:30:52 -0700 Subject: [PATCH 0971/1621] Fix test regression --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 07162c3cd1..b6ae91844a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -55,7 +55,12 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestExitWithoutSave() { - AddStep("exit without save", () => Editor.Exit()); + AddStep("exit without save", () => + { + Editor.Exit(); + DialogOverlay.CurrentDialog.PerformOkAction(); + }); + AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen()); AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true); } From 6fd020d91d76c3017fed6e4f99d2228d5d6a391b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 12:00:38 +0900 Subject: [PATCH 0972/1621] Remove unnecessary public accessibility All interface members are implicitly public. --- osu.Game/Rulesets/Mods/IHasSeed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/IHasSeed.cs b/osu.Game/Rulesets/Mods/IHasSeed.cs index b6852d960b..8e317b08be 100644 --- a/osu.Game/Rulesets/Mods/IHasSeed.cs +++ b/osu.Game/Rulesets/Mods/IHasSeed.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { public interface IHasSeed { - public Bindable Seed { get; } + Bindable Seed { get; } } public class SeedSettingsControl : SettingsItem From 54084f8a9c7009766380f8cc12f87aca866030f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 13:40:05 +0900 Subject: [PATCH 0973/1621] Move `SeedSettingsControl` to own file --- osu.Game/Rulesets/Mods/IHasSeed.cs | 82 ----------------- osu.Game/Rulesets/Mods/SeedSettingsControl.cs | 92 +++++++++++++++++++ 2 files changed, 92 insertions(+), 82 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/SeedSettingsControl.cs diff --git a/osu.Game/Rulesets/Mods/IHasSeed.cs b/osu.Game/Rulesets/Mods/IHasSeed.cs index 8e317b08be..001a9d214c 100644 --- a/osu.Game/Rulesets/Mods/IHasSeed.cs +++ b/osu.Game/Rulesets/Mods/IHasSeed.cs @@ -2,11 +2,6 @@ // 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.UserInterface; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods { @@ -14,81 +9,4 @@ namespace osu.Game.Rulesets.Mods { Bindable Seed { get; } } - - public class SeedSettingsControl : SettingsItem - { - protected override Drawable CreateControl() => new SeedControl - { - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 5 } - }; - - private sealed class SeedControl : CompositeDrawable, IHasCurrentValue - { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current - { - get => current; - set - { - current.Current = value; - seedNumberBox.Text = value.Value.ToString(); - } - } - - private readonly OsuNumberBox seedNumberBox; - - public SeedControl() - { - AutoSizeAxes = Axes.Y; - - InternalChildren = new[] - { - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), - new Dimension(GridSizeMode.Relative, 0.25f) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - seedNumberBox = new OsuNumberBox - { - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true - } - } - } - } - }; - - seedNumberBox.Current.BindValueChanged(e => - { - int? value = null; - - if (int.TryParse(e.NewValue, out var intVal)) - value = intVal; - - current.Value = value; - }); - } - - protected override void Update() - { - if (current.Value == null) - seedNumberBox.Text = current.Current.Value.ToString(); - } - } - } } diff --git a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs new file mode 100644 index 0000000000..5c57717d93 --- /dev/null +++ b/osu.Game/Rulesets/Mods/SeedSettingsControl.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 osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// A settings control for use by mods which have a customisable seed value. + /// + public class SeedSettingsControl : SettingsItem + { + protected override Drawable CreateControl() => new SeedControl + { + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 5 } + }; + + private sealed class SeedControl : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current; + set + { + current.Current = value; + seedNumberBox.Text = value.Value.ToString(); + } + } + + private readonly OsuNumberBox seedNumberBox; + + public SeedControl() + { + AutoSizeAxes = Axes.Y; + + InternalChildren = new[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 2), + new Dimension(GridSizeMode.Relative, 0.25f) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + seedNumberBox = new OsuNumberBox + { + RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true + } + } + } + } + }; + + seedNumberBox.Current.BindValueChanged(e => + { + int? value = null; + + if (int.TryParse(e.NewValue, out var intVal)) + value = intVal; + + current.Value = value; + }); + } + + protected override void Update() + { + if (current.Value == null) + seedNumberBox.Text = current.Current.Value.ToString(); + } + } + } +} From bae42a89089559b7beecec677664fd57aa8705e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 14:07:37 +0900 Subject: [PATCH 0974/1621] Add inline comment explaining why the height is set lower --- osu.Game/Configuration/SettingSourceAttribute.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index cd0652f401..cee5883d9a 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -192,6 +192,7 @@ namespace osu.Game.Configuration private class ModDropdownControl : DropdownControl { + // Set low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). protected override int MenuMaxHeight => 100; } } From e1b2c63e0941a08606f95bb08d28712c73576582 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 14:08:24 +0900 Subject: [PATCH 0975/1621] Add `IApplicableToBeatmapProcessor` mod interface --- osu.Game/Beatmaps/WorkingBeatmap.cs | 3 +++ .../Mods/IApplicableToBeatmapProcessor.cs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 712a3dd33a..662d24cc83 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -133,6 +133,9 @@ namespace osu.Game.Beatmaps IBeatmapProcessor processor = rulesetInstance.CreateBeatmapProcessor(converted); + foreach (var mod in mods.OfType()) + mod.ApplyToBeatmapProcessor(processor); + processor?.PreProcess(); // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs new file mode 100644 index 0000000000..e23a5d8d99 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.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.Game.Beatmaps; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// Interface for a that applies changes to a . + /// + public interface IApplicableToBeatmapProcessor : IApplicableMod + { + /// + /// Applies this to a . + /// + void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor); + } +} From a0fd7f72ac8968bfa6d7a430ab571bdf3e558582 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 14:11:25 +0900 Subject: [PATCH 0976/1621] Use IApplicableToBeatmapProcessor in CatchModHardRock --- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs | 8 ++++---- osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs | 8 ++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index fac5d03833..3a5322ce82 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -8,7 +8,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.MathUtils; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Beatmaps @@ -17,6 +16,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { public const int RNG_SEED = 1337; + public bool HardRockOffsets { get; set; } + public CatchBeatmapProcessor(IBeatmap beatmap) : base(beatmap) { @@ -43,11 +44,10 @@ namespace osu.Game.Rulesets.Catch.Beatmaps } } - public static void ApplyPositionOffsets(IBeatmap beatmap, params Mod[] mods) + public void ApplyPositionOffsets(IBeatmap beatmap) { var rng = new FastRandom(RNG_SEED); - bool shouldApplyHardRockOffset = mods.Any(m => m is ModHardRock); float? lastPosition = null; double lastStartTime = 0; @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps switch (obj) { case Fruit fruit: - if (shouldApplyHardRockOffset) + if (HardRockOffsets) applyHardRockOffset(fruit, ref lastPosition, ref lastStartTime, rng); break; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index 0dde6aa06e..68b6ce96a3 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -7,10 +7,14 @@ using osu.Game.Rulesets.Catch.Beatmaps; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModHardRock : ModHardRock, IApplicableToBeatmap + public class CatchModHardRock : ModHardRock, IApplicableToBeatmapProcessor { public override double ScoreMultiplier => 1.12; - public void ApplyToBeatmap(IBeatmap beatmap) => CatchBeatmapProcessor.ApplyPositionOffsets(beatmap, this); + public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor) + { + var catchProcessor = (CatchBeatmapProcessor)beatmapProcessor; + catchProcessor.HardRockOffsets = true; + } } } From ad60b9d5a08000018b5a21b1a6656ce43884fe40 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 23 Jun 2021 14:20:57 +0900 Subject: [PATCH 0977/1621] Allow catch difficulty adjust to enable hard rock offsets --- .../Mods/CatchModDifficultyAdjust.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 5f1736450a..0eec47ed09 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -5,11 +5,12 @@ using System.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModDifficultyAdjust : ModDifficultyAdjust + public class CatchModDifficultyAdjust : ModDifficultyAdjust, IApplicableToBeatmapProcessor { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] public BindableNumber CircleSize { get; } = new BindableFloatWithLimitExtension @@ -31,6 +32,9 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; + [SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")] + public BindableBool HardRockOffsets { get; } = new BindableBool(); + protected override void ApplyLimits(bool extended) { base.ApplyLimits(extended); @@ -70,5 +74,11 @@ namespace osu.Game.Rulesets.Catch.Mods ApplySetting(CircleSize, cs => difficulty.CircleSize = cs); ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar); } + + public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor) + { + var catchProcessor = (CatchBeatmapProcessor)beatmapProcessor; + catchProcessor.HardRockOffsets = HardRockOffsets.Value; + } } } From c9a203cc11f12532054493629617e21daaba4b21 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 15:03:34 +0900 Subject: [PATCH 0978/1621] Move collections db in-place --- osu.Game/Collections/CollectionManager.cs | 41 ++++++++++++++++------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index b53cc659f7..b2f36c75f4 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -257,27 +257,42 @@ namespace osu.Game.Collections { Interlocked.Increment(ref lastSave); + // This is NOT thread-safe!! try { - // This is NOT thread-safe!! + var tempPath = Path.GetTempFileName(); - using (var sw = new SerializationWriter(storage.GetStream(database_name, FileAccess.Write))) + using (var ms = new MemoryStream()) { - sw.Write(database_version); - - var collectionsCopy = Collections.ToArray(); - sw.Write(collectionsCopy.Length); - - foreach (var c in collectionsCopy) + using (var sw = new SerializationWriter(ms, true)) { - sw.Write(c.Name.Value); + sw.Write(database_version); - var beatmapsCopy = c.Beatmaps.ToArray(); - sw.Write(beatmapsCopy.Length); + var collectionsCopy = Collections.ToArray(); + sw.Write(collectionsCopy.Length); - foreach (var b in beatmapsCopy) - sw.Write(b.MD5Hash); + foreach (var c in collectionsCopy) + { + sw.Write(c.Name.Value); + + var beatmapsCopy = c.Beatmaps.ToArray(); + sw.Write(beatmapsCopy.Length); + + foreach (var b in beatmapsCopy) + sw.Write(b.MD5Hash); + } } + + using (var fs = File.OpenWrite(tempPath)) + ms.WriteTo(fs); + + var storagePath = storage.GetFullPath(database_name); + var storageBackupPath = storage.GetFullPath($"{database_name}.bak"); + if (File.Exists(storageBackupPath)) + File.Delete(storageBackupPath); + if (File.Exists(storagePath)) + File.Move(storagePath, storageBackupPath); + File.Move(tempPath, storagePath); } if (saveFailures < 10) From e4d17bd75773c52b61ec26c610483aec2ba4b34d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 15:06:28 +0900 Subject: [PATCH 0979/1621] Remove commented code This will be reverted in a future change, no need to keep it around. --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index d2e07c7d15..4a33f9e296 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -205,16 +205,6 @@ namespace osu.Game.Overlays.Toolbar var realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.ActionInt == (int)Hotkey.Value); - // TODO: temporarily disabled to avoid crashes when querying after ExecutionState is changed. - // if (realmKeyBinding != null) - // { - // realmKeyBinding.PropertyChanged += (sender, args) => - // { - // if (args.PropertyName == nameof(realmKeyBinding.KeyCombinationString)) - // updateKeyBindingTooltip(); - // }; - // } - if (realmKeyBinding != null) { var keyBindingString = realmKeyBinding.KeyCombination.ReadableString(); From 5828606c1332152b71c942ae2b06d7d98dc445ad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 15:09:42 +0900 Subject: [PATCH 0980/1621] Prefer using the backup file if it exists --- osu.Game/Collections/CollectionManager.cs | 30 ++++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index b2f36c75f4..04ea7eaebf 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -35,6 +35,7 @@ namespace osu.Game.Collections private const int database_version = 30000000; private const string database_name = "collection.db"; + private const string database_backup_name = "collection.db.bak"; public readonly BindableList Collections = new BindableList(); @@ -56,11 +57,14 @@ namespace osu.Game.Collections { Collections.CollectionChanged += collectionsChanged; - if (storage.Exists(database_name)) + // If a backup file exists, it means the previous write operation didn't complete successfully. Always prefer the backup file in such a case. + string filename = storage.Exists(database_backup_name) ? database_backup_name : database_name; + + if (storage.Exists(filename)) { List beatmapCollections; - using (var stream = storage.GetStream(database_name)) + using (var stream = storage.GetStream(filename)) beatmapCollections = readCollections(stream); // intentionally fire-and-forget async. @@ -286,13 +290,21 @@ namespace osu.Game.Collections using (var fs = File.OpenWrite(tempPath)) ms.WriteTo(fs); - var storagePath = storage.GetFullPath(database_name); - var storageBackupPath = storage.GetFullPath($"{database_name}.bak"); - if (File.Exists(storageBackupPath)) - File.Delete(storageBackupPath); - if (File.Exists(storagePath)) - File.Move(storagePath, storageBackupPath); - File.Move(tempPath, storagePath); + var databasePath = storage.GetFullPath(database_name); + var databaseBackupPath = storage.GetFullPath(database_backup_name); + + // Back up the existing database, clearing any existing backup. + if (File.Exists(databaseBackupPath)) + File.Delete(databaseBackupPath); + if (File.Exists(databasePath)) + File.Move(databasePath, databaseBackupPath); + + // Move the new database in-place of the existing one. + File.Move(tempPath, databasePath); + + // If everything succeeded up to this point, remove the backup file. + if (File.Exists(databaseBackupPath)) + File.Delete(databaseBackupPath); } if (saveFailures < 10) From 0510282dcbe1a3ad581d01ed57d1bfcfea431c18 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 15:10:03 +0900 Subject: [PATCH 0981/1621] Add missing ctor --- osu.Game/IO/Legacy/SerializationWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/IO/Legacy/SerializationWriter.cs b/osu.Game/IO/Legacy/SerializationWriter.cs index bb8014fe54..9ebeaf616e 100644 --- a/osu.Game/IO/Legacy/SerializationWriter.cs +++ b/osu.Game/IO/Legacy/SerializationWriter.cs @@ -18,8 +18,8 @@ namespace osu.Game.IO.Legacy /// handle null strings and simplify use with ISerializable. public class SerializationWriter : BinaryWriter { - public SerializationWriter(Stream s) - : base(s, Encoding.UTF8) + public SerializationWriter(Stream s, bool leaveOpen = false) + : base(s, Encoding.UTF8, leaveOpen) { } From 0db06c727b47c43b0207af22054a26c774c8c5b9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Jun 2021 09:41:45 +0300 Subject: [PATCH 0982/1621] Dispose resource stores on finalizer --- osu.Game/Skinning/RulesetResourcesSkin.cs | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game/Skinning/RulesetResourcesSkin.cs b/osu.Game/Skinning/RulesetResourcesSkin.cs index 1905a8a899..60b22a8e67 100644 --- a/osu.Game/Skinning/RulesetResourcesSkin.cs +++ b/osu.Game/Skinning/RulesetResourcesSkin.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.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -47,5 +48,34 @@ namespace osu.Game.Skinning } public IBindable GetConfig(TLookup lookup) => null; + + #region Disposal + + ~RulesetResourcesSkin() + { + // required to potentially clean up sample store from audio hierarchy. + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool isDisposed; + + protected virtual void Dispose(bool isDisposing) + { + if (isDisposed) + return; + + isDisposed = true; + + rulesetTextures?.Dispose(); + rulesetSamples?.Dispose(); + } + + #endregion } } From 2e6800f5862f1815727b967a444f951fdbc89c1f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Jun 2021 09:52:00 +0300 Subject: [PATCH 0983/1621] Enable NRT in `RulesetResourcesSkin` --- osu.Game/Skinning/RulesetResourcesSkin.cs | 29 +++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/RulesetResourcesSkin.cs b/osu.Game/Skinning/RulesetResourcesSkin.cs index 60b22a8e67..5cf2eec338 100644 --- a/osu.Game/Skinning/RulesetResourcesSkin.cs +++ b/osu.Game/Skinning/RulesetResourcesSkin.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -20,26 +22,29 @@ namespace osu.Game.Skinning /// public class RulesetResourcesSkin : ISkin { - private readonly TextureStore rulesetTextures; - private readonly ISampleStore rulesetSamples; + private readonly TextureStore? textures; + private readonly ISampleStore? samples; public RulesetResourcesSkin(Ruleset ruleset, GameHost host, AudioManager audio) { - IResourceStore rulesetResources = ruleset.CreateResourceStore(); + IResourceStore? resources = ruleset.CreateResourceStore(); - rulesetTextures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(rulesetResources, @"Textures"))); - rulesetSamples = audio.GetSampleStore(new NamespacedResourceStore(rulesetResources, @"Samples")); + if (resources != null) + { + textures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); + samples = audio.GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); + } } - public Drawable GetDrawableComponent(ISkinComponent component) => null; + public Drawable? GetDrawableComponent(ISkinComponent component) => null; - public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => rulesetTextures.Get(componentName, wrapModeS, wrapModeT); + public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => textures?.Get(componentName, wrapModeS, wrapModeT); - public ISample GetSample(ISampleInfo sampleInfo) + public ISample? GetSample(ISampleInfo sampleInfo) { foreach (var lookup in sampleInfo.LookupNames) { - ISample sample = rulesetSamples.Get(lookup); + ISample? sample = samples?.Get(lookup); if (sample != null) return sample; } @@ -47,7 +52,7 @@ namespace osu.Game.Skinning return null; } - public IBindable GetConfig(TLookup lookup) => null; + public IBindable? GetConfig(TLookup lookup) => null; #region Disposal @@ -72,8 +77,8 @@ namespace osu.Game.Skinning isDisposed = true; - rulesetTextures?.Dispose(); - rulesetSamples?.Dispose(); + textures?.Dispose(); + samples?.Dispose(); } #endregion From 53fa22988097e9f22d6b16f8de7ce512a1a1bbab Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Jun 2021 10:09:25 +0300 Subject: [PATCH 0984/1621] Add ruleset resources skin before `SkinManager.DefaultSkin` --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index e46ba6d857..c117df4b09 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -50,6 +50,9 @@ namespace osu.Game.Skinning [Resolved] private AudioManager audio { get; set; } + [Resolved] + private SkinManager skinManager { get; set; } + [Resolved] private ISkinSource skinSource { get; set; } @@ -84,7 +87,7 @@ namespace osu.Game.Skinning } } - SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); + SkinSources.Insert(SkinSources.IndexOf(skinManager.DefaultSkin), new RulesetResourcesSkin(Ruleset, host, audio)); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) From e7981eae9bbca5e40afbe9103628cfe17f9af9ba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 16:14:05 +0900 Subject: [PATCH 0985/1621] Safeguard load procedure a bit more --- osu.Game/Collections/CollectionManager.cs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 04ea7eaebf..e6e8bedcf4 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -55,16 +55,26 @@ namespace osu.Game.Collections [BackgroundDependencyLoader] private void load() { + if (storage.Exists(database_backup_name)) + { + // If a backup file exists, it means the previous write operation didn't run to completion. + // Always prefer the backup file in such a case as it's the most recent copy that is guaranteed to not be malformed. + // + // The database is saved 100ms after any change, and again when the game is closed, so there shouldn't be a large diff between the two files in the worst case. + if (storage.Exists(database_name)) + storage.Delete(database_name); + File.Copy(storage.GetFullPath(database_backup_name), storage.GetFullPath(database_name)); + } + + // Only accept changes after/if the above succeeds to prevent simultaneously accessing either file. + // Todo: This really should not be delayed like this, but is unlikely to cause issues because CollectionManager loads very early in execution. Collections.CollectionChanged += collectionsChanged; - // If a backup file exists, it means the previous write operation didn't complete successfully. Always prefer the backup file in such a case. - string filename = storage.Exists(database_backup_name) ? database_backup_name : database_name; - - if (storage.Exists(filename)) + if (storage.Exists(database_name)) { List beatmapCollections; - using (var stream = storage.GetStream(filename)) + using (var stream = storage.GetStream(database_name)) beatmapCollections = readCollections(stream); // intentionally fire-and-forget async. From 2f657b972ddba9fefb4ada925f36067c3b275e99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 16:29:18 +0900 Subject: [PATCH 0986/1621] Remove local configuration of rider data sources --- .idea/.idea.osu.Desktop/.idea/dataSources.xml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .idea/.idea.osu.Desktop/.idea/dataSources.xml diff --git a/.idea/.idea.osu.Desktop/.idea/dataSources.xml b/.idea/.idea.osu.Desktop/.idea/dataSources.xml deleted file mode 100644 index 10f8c1c84d..0000000000 --- a/.idea/.idea.osu.Desktop/.idea/dataSources.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - sqlite.xerial - true - org.sqlite.JDBC - jdbc:sqlite:$USER_HOME$/.local/share/osu/client.db - - - - - - \ No newline at end of file From 28f4c56cd6531b64f6631076fa911d62a2867b43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 16:30:17 +0900 Subject: [PATCH 0987/1621] Fix minor typo in comment --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3a08ef684f..6195d8e1ea 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -258,7 +258,7 @@ namespace osu.Game dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, true)); - // this should likely be moved to ArchiveModelManager when another case appers where it is necessary + // this should likely be moved to ArchiveModelManager when another case appears where it is necessary // to have inter-dependent model managers. this could be obtained with an IHasForeign interface to // allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete. List getBeatmapScores(BeatmapSetInfo set) From d48446990616a5d10c81b35a4aa4470283dc5757 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Jun 2021 10:44:21 +0300 Subject: [PATCH 0988/1621] Handle case where `SkinManager` sources aren't part of `AllSources` In tests. --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index c117df4b09..83e2d398f9 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -87,7 +87,11 @@ namespace osu.Game.Skinning } } - SkinSources.Insert(SkinSources.IndexOf(skinManager.DefaultSkin), new RulesetResourcesSkin(Ruleset, host, audio)); + var defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + if (defaultSkinIndex >= 0) + SkinSources.Insert(defaultSkinIndex, new RulesetResourcesSkin(Ruleset, host, audio)); + else + SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) From 5a031eada861095340b1d9c7ae428b9318f240a7 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 23 Jun 2021 16:22:10 +0800 Subject: [PATCH 0989/1621] Revert "Display results after fail" This commit reverts 7815b3c7 --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableFailOverride.cs | 5 ----- osu.Game/Rulesets/Mods/ModAutoplay.cs | 2 -- osu.Game/Rulesets/Mods/ModBlockFail.cs | 1 - osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs | 1 - osu.Game/Rulesets/Mods/ModFailCondition.cs | 1 - osu.Game/Screens/Play/Player.cs | 10 ++-------- 8 files changed, 2 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 2b92174b29..aac830801b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -28,8 +28,6 @@ namespace osu.Game.Rulesets.Osu.Mods public bool RestartOnFail => false; - public bool DisplayResultsOnFail => false; - private OsuInputManager inputManager; private IFrameStableClock gameplayClock; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 75089fe237..bf31839991 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -51,8 +51,6 @@ namespace osu.Game.Rulesets.Osu.Mods public bool RestartOnFail => false; - public bool DisplayResultsOnFail => true; - /// /// Jump distance for circles in the last combo /// diff --git a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs index b7d306e57b..8c99d739cb 100644 --- a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs +++ b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs @@ -18,10 +18,5 @@ namespace osu.Game.Rulesets.Mods /// Whether we want to restart on fail. Only used if returns true. /// bool RestartOnFail { get; } - - /// - /// Whether to proceed to results screen on fail. Only used if returns true and is false. - /// - bool DisplayResultsOnFail { get; } } } diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index e5652a8609..b84b5671e1 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -24,8 +24,6 @@ namespace osu.Game.Rulesets.Mods public bool RestartOnFail => false; - public bool DisplayResultsOnFail => false; - public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) }; public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs index 973a192378..1fde5abad4 100644 --- a/osu.Game/Rulesets/Mods/ModBlockFail.cs +++ b/osu.Game/Rulesets/Mods/ModBlockFail.cs @@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Mods public bool PerformFail() => false; public bool RestartOnFail => false; - public bool DisplayResultsOnFail => false; public void ReadFromConfig(OsuConfigManager config) { diff --git a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs index 91508b7e70..2ac0f59d84 100644 --- a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs +++ b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs @@ -41,7 +41,6 @@ namespace osu.Game.Rulesets.Mods } public bool RestartOnFail => false; - public bool DisplayResultsOnFail => false; public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs index 012a0f73ff..c0d7bae2b2 100644 --- a/osu.Game/Rulesets/Mods/ModFailCondition.cs +++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs @@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Mods public virtual bool PerformFail() => true; public virtual bool RestartOnFail => true; - public virtual bool DisplayResultsOnFail => false; public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4726d54ebc..f9036780aa 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -640,7 +640,7 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; - if (!ScoreProcessor.HasCompleted.Value && !HealthProcessor.HasFailed) + if (!ScoreProcessor.HasCompleted.Value) { completionProgressDelegate?.Cancel(); completionProgressDelegate = null; @@ -653,7 +653,7 @@ namespace osu.Game.Screens.Play throw new InvalidOperationException($"{nameof(updateCompletionState)} was fired more than once"); // Only show the completion screen if the player hasn't failed - if (HealthProcessor.HasFailed && !Mods.Value.OfType().Any(m => m.DisplayResultsOnFail)) + if (HealthProcessor.HasFailed) return; ValidForResume = false; @@ -751,12 +751,6 @@ namespace osu.Game.Screens.Play // Called back when the transform finishes private void onFailComplete() { - if (Mods.Value.OfType().Any(m => m.DisplayResultsOnFail)) - { - updateCompletionState(true); - return; - } - GameplayClockContainer.Stop(); FailOverlay.Retries = RestartCount; From 6dc5f406b27818051aa6423b8f7e1187414408df Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 23 Jun 2021 16:29:36 +0800 Subject: [PATCH 0990/1621] Implement IMutateApproachCircles --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index bf31839991..559ad31751 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -31,7 +31,8 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModTarget : ModWithVisibilityAdjustment, IApplicableToDrawableRuleset, - IApplicableToHealthProcessor, IApplicableToDifficulty, IApplicableFailOverride, IHasSeed + IApplicableToHealthProcessor, IApplicableToDifficulty, IApplicableFailOverride, + IHasSeed, IMutateApproachCircles { public override string Name => "Target"; public override string Acronym => "TP"; @@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Practice keeping up with the beat of the song."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable) }; + public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SeedSettingsControl))] public Bindable Seed { get; } = new Bindable From 7767e2e77fbc2919b3ec9cce8fcf1f99590dc1cf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 17:34:30 +0900 Subject: [PATCH 0991/1621] Add to tooltip --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 0eec47ed09..98ddcaa21d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -49,12 +49,14 @@ namespace osu.Game.Rulesets.Catch.Mods { string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}"; string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}"; + string spicyPatterns = HardRockOffsets.IsDefault ? string.Empty : $"Spicy patterns"; return string.Join(", ", new[] { circleSize, base.SettingDescription, - approachRate + approachRate, + spicyPatterns, }.Where(s => !string.IsNullOrEmpty(s))); } } From ed0552a9e8cce45220498581eec036f9640fde3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 17:19:18 +0900 Subject: [PATCH 0992/1621] Add failing test for FK constraint conflict on reimporting modified beatmap with scores present --- .../Beatmaps/IO/ImportBeatmapTest.cs | 72 ++++++++++++++++++- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 16 ++--- 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0d117f8755..66b5cbed0c 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -19,7 +19,9 @@ using osu.Game.Database; using osu.Game.IO; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Scoring; using osu.Game.Tests.Resources; +using osu.Game.Tests.Scores.IO; using osu.Game.Users; using SharpCompress.Archives; using SharpCompress.Archives.Zip; @@ -185,10 +187,58 @@ namespace osu.Game.Tests.Beatmaps.IO } } - private string hashFile(string filename) + [Test] + public async Task TestImportThenImportWithChangedHashedFile() { - using (var s = File.OpenRead(filename)) - return s.ComputeMD5Hash(); + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + { + try + { + var osu = LoadOsuIntoHost(host); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + var imported = await LoadOszIntoOsu(osu); + + await createScoreForBeatmap(osu, imported.Beatmaps.First()); + + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + // arbitrary write to hashed file + // this triggers the special BeatmapManager.PreImport deletion/replacement flow. + using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).AppendText()) + await sw.WriteLineAsync("// changed"); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); + } + + var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp)); + + ensureLoaded(osu); + + // check the newly "imported" beatmap is not the original. + Assert.IsTrue(imported.ID != importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + } + finally + { + Directory.Delete(extractedFolder, true); + } + } + finally + { + host.Exit(); + } + } } [Test] @@ -895,6 +945,16 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); } + private Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap) + { + return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo + { + OnlineScoreID = 2, + Beatmap = beatmap, + BeatmapInfoID = beatmap.ID + }, new ImportScoreTest.TestArchiveReader()); + } + private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) { var manager = osu.Dependencies.Get(); @@ -904,6 +964,12 @@ namespace osu.Game.Tests.Beatmaps.IO : manager.GetAllUsableBeatmapSets().Count); } + private string hashFile(string filename) + { + using (var s = File.OpenRead(filename)) + return s.ComputeMD5Hash(); + } + private void checkBeatmapCount(OsuGameBase osu, int expected) { Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count); diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 7522aca5dc..cd7d744f53 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Scores.IO OnlineScoreID = 12345, }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Rank, imported.Rank); Assert.AreEqual(toImport.TotalScore, imported.TotalScore); @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Scores.IO Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]); Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]); @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await loadScoreIntoOsu(osu, toImport); + var imported = await LoadScoreIntoOsu(osu, toImport); var beatmapManager = osu.Dependencies.Get(); var scoreManager = osu.Dependencies.Get(); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Scores.IO beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.Beatmap.ID))); Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); - var secondImport = await loadScoreIntoOsu(osu, imported); + var secondImport = await LoadScoreIntoOsu(osu, imported); Assert.That(secondImport, Is.Null); } finally @@ -163,7 +163,7 @@ namespace osu.Game.Tests.Scores.IO { var osu = LoadOsuIntoHost(host, true); - await loadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); + await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); var scoreManager = osu.Dependencies.Get(); @@ -177,7 +177,7 @@ namespace osu.Game.Tests.Scores.IO } } - private async Task loadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) + public static async Task LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { var beatmapManager = osu.Dependencies.Get(); @@ -190,7 +190,7 @@ namespace osu.Game.Tests.Scores.IO return scoreManager.GetAllUsableScores().FirstOrDefault(); } - private class TestArchiveReader : ArchiveReader + internal class TestArchiveReader : ArchiveReader { public TestArchiveReader() : base("test_archive") From dcba7bf779fa2ab78a884cc17671eefa15a71082 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 17:19:32 +0900 Subject: [PATCH 0993/1621] Fix import flow potentially hitting foreign key constraint --- osu.Game/Beatmaps/BeatmapManager.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..e93d8c6eb5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -181,8 +181,13 @@ namespace osu.Game.Beatmaps if (existingOnlineId != null) { Delete(existingOnlineId); - beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); - LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged."); + + // in order to avoid a unique key constraint, immediately remove the online ID from the previous set. + existingOnlineId.OnlineBeatmapSetID = null; + foreach (var b in existingOnlineId.Beatmaps) + b.OnlineBeatmapID = null; + + LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been deleted."); } } } From f6180b7e6a8adfa6560da82b4fe6805d5577f61e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 17:37:24 +0900 Subject: [PATCH 0994/1621] Mark `static` methods as such --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 66b5cbed0c..3b355da359 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -945,7 +945,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); } - private Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap) + private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap) { return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo { @@ -955,7 +955,7 @@ namespace osu.Game.Tests.Beatmaps.IO }, new ImportScoreTest.TestArchiveReader()); } - private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) + private static void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) { var manager = osu.Dependencies.Get(); @@ -964,18 +964,18 @@ namespace osu.Game.Tests.Beatmaps.IO : manager.GetAllUsableBeatmapSets().Count); } - private string hashFile(string filename) + private static string hashFile(string filename) { using (var s = File.OpenRead(filename)) return s.ComputeMD5Hash(); } - private void checkBeatmapCount(OsuGameBase osu, int expected) + private static void checkBeatmapCount(OsuGameBase osu, int expected) { Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count); } - private void checkSingleReferencedFileCount(OsuGameBase osu, int expected) + private static void checkSingleReferencedFileCount(OsuGameBase osu, int expected) { Assert.AreEqual(expected, osu.Dependencies.Get().QueryFiles(f => f.ReferenceCount == 1).Count()); } From 6215f2d42beb416dc3a645e9f45cca77624a8d15 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 17:40:11 +0900 Subject: [PATCH 0995/1621] Remove unnecessary string interpolation --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 98ddcaa21d..bd7a1df2e4 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Mods { string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}"; string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}"; - string spicyPatterns = HardRockOffsets.IsDefault ? string.Empty : $"Spicy patterns"; + string spicyPatterns = HardRockOffsets.IsDefault ? string.Empty : "Spicy patterns"; return string.Join(", ", new[] { From b7dd26612d2ca78fe09a38980e3f1c0b9c472cd7 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 23 Jun 2021 16:50:05 +0800 Subject: [PATCH 0996/1621] Reordered things and added regions --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 185 ++++++++++++--------- 1 file changed, 106 insertions(+), 79 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 559ad31751..6057b134e3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods Value = null }; - public bool RestartOnFail => false; + #region Constants /// /// Jump distance for circles in the last combo @@ -82,12 +82,19 @@ namespace osu.Game.Rulesets.Osu.Mods /// private const double undim_duration = 96; + #endregion + + #region Private Fields + private ControlPointInfo controlPointInfo; - public bool PerformFail() - { - return true; - } + #endregion + + #region Sudden Death (IApplicableFailOverride) + + public bool PerformFail() => true; + + public bool RestartOnFail => false; public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { @@ -97,6 +104,55 @@ namespace osu.Game.Rulesets.Osu.Mods && !result.IsHit; } + #endregion + + #region Reduce AR (IApplicableToDifficulty) + + public void ReadFromDifficulty(BeatmapDifficulty difficulty) + { + } + + public void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + // Decrease AR to increase preempt time + difficulty.ApproachRate *= 0.5f; + } + + #endregion + + #region Circle Transforms (ModWithVisibilityAdjustment) + + protected override void ApplyIncreasedVisibilityState(DrawableHitObject drawable, ArmedState state) + { + } + + protected override void ApplyNormalVisibilityState(DrawableHitObject drawable, ArmedState state) + { + if (!(drawable is DrawableHitCircle circle)) return; + + var h = (OsuHitObject)drawable.HitObject; + + using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) + { + drawable.ScaleTo(0.5f) + .Then().ScaleTo(1f, h.TimePreempt); + + var colour = drawable.Colour; + + var avgColour = colour.AverageColour.Linear; + drawable.FadeColour(new Color4(avgColour.R * 0.45f, avgColour.G * 0.45f, avgColour.B * 0.45f, avgColour.A)) + .Then().Delay(h.TimePreempt - controlPointInfo.TimingPointAt(h.StartTime).BeatLength - undim_duration) + .FadeColour(colour, undim_duration); + + // remove approach circles + circle.ApproachCircle.Hide(); + } + } + + #endregion + + #region Beatmap Generation (IApplicableToBeatmap) + public override void ApplyToBeatmap(IBeatmap beatmap) { Seed.Value ??= RNG.Next(); @@ -128,55 +184,6 @@ namespace osu.Game.Rulesets.Osu.Mods base.ApplyToBeatmap(beatmap); } - public void ReadFromDifficulty(BeatmapDifficulty difficulty) - { - } - - public void ApplyToDifficulty(BeatmapDifficulty difficulty) - { - // Decrease AR to increase preempt time - difficulty.ApproachRate *= 0.5f; - } - - // Background metronome - - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - drawableRuleset.Overlays.Add(new TargetBeatContainer()); - } - - protected override void ApplyIncreasedVisibilityState(DrawableHitObject drawable, ArmedState state) - { - } - - protected override void ApplyNormalVisibilityState(DrawableHitObject drawable, ArmedState state) - { - if (!(drawable is DrawableHitCircle circle)) return; - - var h = (OsuHitObject)drawable.HitObject; - - using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) - { - drawable.ScaleTo(0.5f) - .Then().ScaleTo(1f, h.TimePreempt); - - var colour = drawable.Colour; - - var avgColour = colour.AverageColour.Linear; - drawable.FadeColour(new Color4(avgColour.R * 0.45f, avgColour.G * 0.45f, avgColour.B * 0.45f, avgColour.A)) - .Then().Delay(h.TimePreempt - controlPointInfo.TimingPointAt(h.StartTime).BeatLength - undim_duration) - .FadeColour(colour, undim_duration); - - // remove approach circles - circle.ApproachCircle.Hide(); - } - } - - private static float map(float value, float fromLow, float fromHigh, float toLow, float toHigh) - { - return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow; - } - private IEnumerable generateBeats(IBeatmap beatmap, IReadOnlyCollection origHitObjects) { var startTime = origHitObjects.First().StartTime; @@ -335,6 +342,47 @@ namespace osu.Game.Rulesets.Osu.Mods } } + #endregion + + #region Metronome (IApplicableToDrawableRuleset) + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + drawableRuleset.Overlays.Add(new TargetBeatContainer()); + } + + public class TargetBeatContainer : BeatSyncedContainer + { + private PausableSkinnableSound sample; + + public TargetBeatContainer() + { + Divisor = 1; + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + if (!IsBeatSyncedWithTrack) return; + + sample?.Play(); + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + sample = new PausableSkinnableSound(new SampleInfo("Gameplay/nightcore-hat")) // todo: use another sample + }; + } + } + + #endregion + + #region Helper Subroutines + /// /// Get samples (if any) for a specific point in time. /// @@ -470,32 +518,11 @@ namespace osu.Game.Rulesets.Osu.Mods obj.Position = position; } - public class TargetBeatContainer : BeatSyncedContainer + private static float map(float value, float fromLow, float fromHigh, float toLow, float toHigh) { - private PausableSkinnableSound sample; - - public TargetBeatContainer() - { - Divisor = 1; - } - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) - { - base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - - if (!IsBeatSyncedWithTrack) return; - - sample?.Play(); - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChildren = new Drawable[] - { - sample = new PausableSkinnableSound(new SampleInfo("Gameplay/nightcore-hat")) // todo: use another sample - }; - } + return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow; } + + #endregion } } From d148656108320559df37f00998d9346c3e2f41e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 18:08:34 +0900 Subject: [PATCH 0997/1621] Update in line with framework event structural changes (and add unbind) --- osu.Game/OsuGameBase.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 43c81783fe..f81eaa08a5 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -183,11 +183,7 @@ namespace osu.Game dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); - Host.UpdateThreadPausing += () => - { - var blocking = realmFactory.BlockAllOperations(); - Schedule(() => blocking.Dispose()); - }; + Host.UpdateThread.ThreadPausing += onUpdateThreadPausing; AddInternal(realmFactory); @@ -363,6 +359,12 @@ namespace osu.Game Ruleset.BindValueChanged(onRulesetChanged); } + private void onUpdateThreadPausing() + { + var blocking = realmFactory.BlockAllOperations(); + Schedule(() => blocking.Dispose()); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -496,6 +498,9 @@ namespace osu.Game LocalConfig?.Dispose(); contextFactory.FlushConnections(); + + if (Host != null) + Host.UpdateThread.ThreadPausing -= onUpdateThreadPausing; } } } From 1bbfbb0d8e117ac9f5dad5a4f34aa3e1726ada2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 19:30:08 +0900 Subject: [PATCH 0998/1621] Fix test that never should have worked This was only working by luck until now. It was "correctly" matching on null online ID (see logic at https://github.com/ppy/osu/blob/abc96057b2cf50e02e0ec939645f6421684495d4/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs#L199-L207). Now it works by actually matching on the online ID. --- .../TestScenePlaylistsRoomSubScreen.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index a08a91314b..c4da2ab48a 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -111,10 +111,27 @@ namespace osu.Game.Tests.Visual.Playlists public void TestBeatmapUpdatedOnReImport() { BeatmapSetInfo importedSet = null; + TestBeatmap beatmap = null; + + // this step is required to make sure the further imports actually get online IDs. + // all the playlist logic relies on online ID matching. + AddStep("remove all matching online IDs", () => + { + beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo); + + var existing = manager.QueryBeatmapSets(s => s.OnlineBeatmapSetID == beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID).ToList(); + + foreach (var s in existing) + { + s.OnlineBeatmapSetID = null; + foreach (var b in s.Beatmaps) + b.OnlineBeatmapID = null; + manager.Update(s); + } + }); AddStep("import altered beatmap", () => { - var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo); beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result; From 01a5d998a542c7d9e88d071525c7b1cbbbf46fd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 20:29:02 +0900 Subject: [PATCH 0999/1621] 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..d0aff7b15e 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..0418e58593 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..6e2e169149 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 8a2026e3053c9c8bbc7d46c25029fa3d7f7f0506 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 21:26:52 +0900 Subject: [PATCH 1000/1621] Schedule callback instead --- osu.Game/Collections/CollectionManager.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index e6e8bedcf4..fe04c70d62 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -55,6 +55,8 @@ namespace osu.Game.Collections [BackgroundDependencyLoader] private void load() { + Collections.CollectionChanged += collectionsChanged; + if (storage.Exists(database_backup_name)) { // If a backup file exists, it means the previous write operation didn't run to completion. @@ -66,10 +68,6 @@ namespace osu.Game.Collections File.Copy(storage.GetFullPath(database_backup_name), storage.GetFullPath(database_name)); } - // Only accept changes after/if the above succeeds to prevent simultaneously accessing either file. - // Todo: This really should not be delayed like this, but is unlikely to cause issues because CollectionManager loads very early in execution. - Collections.CollectionChanged += collectionsChanged; - if (storage.Exists(database_name)) { List beatmapCollections; @@ -82,7 +80,7 @@ namespace osu.Game.Collections } } - private void collectionsChanged(object sender, NotifyCollectionChangedEventArgs e) + private void collectionsChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => { switch (e.Action) { @@ -106,7 +104,7 @@ namespace osu.Game.Collections } backgroundSave(); - } + }); /// /// Set an endpoint for notifications to be posted to. From 263370e1ff946475f394c74c3c9e6aebab2d064d Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 23 Jun 2021 16:42:14 +0200 Subject: [PATCH 1001/1621] Localize ruleset filter any filter button. --- .../BeatmapSearchRulesetFilterRow.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs index c2d0eea80c..e2c84c537c 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.Framework.Localisation; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; @@ -22,14 +23,21 @@ namespace osu.Game.Overlays.BeatmapListing [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - AddItem(new RulesetInfo - { - Name = @"Any" - }); + AddTabItem(new RulesetFilterTabItemAny()); foreach (var r in rulesets.AvailableRulesets) AddItem(r); } } + + private class RulesetFilterTabItemAny : FilterTabItem + { + protected override LocalisableString LabelFor(RulesetInfo info) => BeatmapsStrings.ModeAny; + + public RulesetFilterTabItemAny() + : base(new RulesetInfo()) + { + } + } } } From d2d0f3bf9ba7e46922c03f95c33d1ca18eb33d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Jun 2021 17:55:40 +0200 Subject: [PATCH 1002/1621] Set more appropriate build time limits for GH Actions workflows --- .github/workflows/ci.yml | 1 + .github/workflows/report-nunit.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed3e99cb61..29cbdd2d37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ jobs: - { prettyname: macOS, fullname: macos-latest } - { prettyname: Linux, fullname: ubuntu-latest } threadingMode: ['SingleThread', 'MultiThreaded'] + timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@v2 diff --git a/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml index 381d2d49c5..e0ccd50989 100644 --- a/.github/workflows/report-nunit.yml +++ b/.github/workflows/report-nunit.yml @@ -21,6 +21,7 @@ jobs: - { prettyname: macOS } - { prettyname: Linux } threadingMode: ['SingleThread', 'MultiThreaded'] + timeout-minutes: 5 steps: - name: Annotate CI run with test results uses: dorny/test-reporter@v1.4.2 From c8022126dce165d7cd0e159e74c5f9cc726ef0b2 Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Wed, 23 Jun 2021 13:39:12 -0700 Subject: [PATCH 1003/1621] Add logo sound case for transitioning to song select --- osu.Game/Screens/Menu/ButtonSystem.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index a836f7bf09..da0edd07db 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -274,6 +274,10 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Play: buttonsPlay.First().Click(); return false; + + // no sound should be played if the logo is clicked on while transitioning to song select + case ButtonSystemState.EnteringMode: + return false; } } From 73590bfca1a4131581aa74c6d7b28a8378729221 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 24 Jun 2021 07:20:31 +0800 Subject: [PATCH 1004/1621] Return an empty array when the sender is from system. --- osu.Game/Overlays/Chat/ChatLine.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index f43420e35e..8666d20159 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -240,6 +240,9 @@ namespace osu.Game.Overlays.Chat { get { + if (sender.Id == User.SYSTEM_USER.Id) + return Array.Empty(); + List items = new List { new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action) From a7ea7b8b0be060f50ba4e20044aa220e79229037 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 09:34:39 +0800 Subject: [PATCH 1005/1621] Use `GetEndTime()` instead of a switch expression --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 6057b134e3..834876b455 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -17,6 +17,7 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; @@ -188,12 +189,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var startTime = origHitObjects.First().StartTime; var endObj = origHitObjects.Last(); - var endTime = endObj switch - { - Slider slider => slider.EndTime, - Spinner spinner => spinner.EndTime, - _ => endObj.StartTime - }; + var endTime = endObj.GetEndTime(); var beats = beatmap.ControlPointInfo.TimingPoints .Where(x => Precision.AlmostBigger(endTime, x.Time)) From dae7b8025da3a867e449fe3025954dbacac449b3 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 09:51:45 +0800 Subject: [PATCH 1006/1621] Converted an inline lambda into a method (`getBeatsForTimingPoint`) --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 30 ++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 834876b455..130489d4c8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -193,19 +193,7 @@ namespace osu.Game.Rulesets.Osu.Mods var beats = beatmap.ControlPointInfo.TimingPoints .Where(x => Precision.AlmostBigger(endTime, x.Time)) - .SelectMany(tp => - { - var tpBeats = new List(); - var currentTime = tp.Time; - - while (Precision.AlmostBigger(endTime, currentTime) && beatmap.ControlPointInfo.TimingPointAt(currentTime) == tp) - { - tpBeats.Add(Math.Floor(currentTime)); - currentTime += tp.BeatLength; - } - - return tpBeats; - }) + .SelectMany(timingPoint => getBeatsForTimingPoint(timingPoint, endTime)) .Where(x => Precision.AlmostBigger(x, startTime)) // Remove beats during breaks .Where(x => !beatmap.Breaks.Any(b => @@ -379,6 +367,22 @@ namespace osu.Game.Rulesets.Osu.Mods #region Helper Subroutines + private IEnumerable getBeatsForTimingPoint(TimingControlPoint timingPoint, double mapEndTime) + { + var beats = new List(); + int i = 0; + var currentTime = timingPoint.Time; + + while (Precision.AlmostBigger(mapEndTime, currentTime) && controlPointInfo.TimingPointAt(currentTime) == timingPoint) + { + beats.Add(Math.Floor(currentTime)); + i++; + currentTime = timingPoint.Time + i * timingPoint.BeatLength; + } + + return beats; + } + /// /// Get samples (if any) for a specific point in time. /// From 3eab540bcca713c73d73f052a4e0f6656b78f1fb Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 10:07:29 +0800 Subject: [PATCH 1007/1621] Converted an inline lambda into a method (`isInsideBreakPeriod`); moved `origHitObjects` to be a private class field --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 47 +++++++++++++++------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 130489d4c8..7a194f4a98 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -13,6 +13,7 @@ using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Timing; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -89,6 +90,8 @@ namespace osu.Game.Rulesets.Osu.Mods private ControlPointInfo controlPointInfo; + private List origHitObjects; + #endregion #region Sudden Death (IApplicableFailOverride) @@ -163,9 +166,9 @@ namespace osu.Game.Rulesets.Osu.Mods if (osuBeatmap.HitObjects.Count == 0) return; controlPointInfo = osuBeatmap.ControlPointInfo; - var origHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); + origHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); - var hitObjects = generateBeats(osuBeatmap, origHitObjects) + var hitObjects = generateBeats(osuBeatmap) .Select(x => { var newCircle = new HitCircle(); @@ -174,9 +177,9 @@ namespace osu.Game.Rulesets.Osu.Mods return (OsuHitObject)newCircle; }).ToList(); - addHitSamples(hitObjects, origHitObjects); + addHitSamples(hitObjects); - fixComboInfo(hitObjects, origHitObjects); + fixComboInfo(hitObjects); randomizeCirclePos(hitObjects); @@ -185,21 +188,17 @@ namespace osu.Game.Rulesets.Osu.Mods base.ApplyToBeatmap(beatmap); } - private IEnumerable generateBeats(IBeatmap beatmap, IReadOnlyCollection origHitObjects) + private IEnumerable generateBeats(IBeatmap beatmap) { var startTime = origHitObjects.First().StartTime; var endObj = origHitObjects.Last(); var endTime = endObj.GetEndTime(); var beats = beatmap.ControlPointInfo.TimingPoints - .Where(x => Precision.AlmostBigger(endTime, x.Time)) + .Where(timingPoint => Precision.AlmostBigger(endTime, timingPoint.Time)) .SelectMany(timingPoint => getBeatsForTimingPoint(timingPoint, endTime)) - .Where(x => Precision.AlmostBigger(x, startTime)) - // Remove beats during breaks - .Where(x => !beatmap.Breaks.Any(b => - Precision.AlmostBigger(x, b.StartTime) - && Precision.AlmostBigger(origHitObjects.First(y => Precision.AlmostBigger(y.StartTime, b.EndTime)).StartTime, x) - )) + .Where(beat => Precision.AlmostBigger(beat, startTime)) + .Where(beat => isInsideBreakPeriod(beatmap.Breaks, beat)) .ToList(); // Remove beats that are too close to the next one (e.g. due to timing point changes) @@ -213,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Mods return beats; } - private void addHitSamples(IEnumerable hitObjects, IReadOnlyList origHitObjects) + private void addHitSamples(IEnumerable hitObjects) { var lastSampleIdx = 0; @@ -243,7 +242,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private void fixComboInfo(List hitObjects, List origHitObjects) + private void fixComboInfo(List hitObjects) { // First follow the combo indices in the original beatmap hitObjects.ForEach(x => @@ -367,6 +366,26 @@ namespace osu.Game.Rulesets.Osu.Mods #region Helper Subroutines + /// + /// Check if a given time is inside a . + /// + /// + /// The given time is also considered to be inside a break if it is earlier than the + /// start time of the first original hit object after the break. + /// + /// The breaks of the beatmap. + /// The time to be checked.= + private bool isInsideBreakPeriod(IEnumerable breaks, double time) + { + return !breaks.Any(breakPeriod => + { + var firstObjAfterBreak = origHitObjects.First(obj => Precision.AlmostBigger(obj.StartTime, breakPeriod.EndTime)); + + return Precision.AlmostBigger(time, breakPeriod.StartTime) + && Precision.AlmostBigger(firstObjAfterBreak.StartTime, time); + }); + } + private IEnumerable getBeatsForTimingPoint(TimingControlPoint timingPoint, double mapEndTime) { var beats = new List(); From 564c72bf74d6e5053f627d539f0118a9c4ee84e8 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 24 Jun 2021 10:10:57 +0800 Subject: [PATCH 1008/1621] compare directly instead of comparing IDs --- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 8666d20159..987dc27802 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -240,7 +240,7 @@ namespace osu.Game.Overlays.Chat { get { - if (sender.Id == User.SYSTEM_USER.Id) + if (sender == User.SYSTEM_USER) return Array.Empty(); List items = new List From 98003ec548f42a8a834ba42ae5c52d03abc24c35 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 10:33:54 +0800 Subject: [PATCH 1009/1621] Avoid modulo when finding slider node index --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 31 +++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 7a194f4a98..31fb70e9c0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -20,6 +20,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -413,30 +414,48 @@ namespace osu.Game.Rulesets.Osu.Mods /// Hit samples private IList getSamplesAtTime(IEnumerable hitObjects, double time) { - var sampleObj = hitObjects.FirstOrDefault(x => + var sampleObj = hitObjects.FirstOrDefault(hitObject => { - if (Precision.AlmostEquals(time, x.StartTime)) return true; + if (Precision.AlmostEquals(time, hitObject.StartTime)) + return true; - if (!(x is Slider s)) + if (!(hitObject is IHasPathWithRepeats s)) return false; - if (!Precision.AlmostBigger(time, s.StartTime) + if (!Precision.AlmostBigger(time, hitObject.StartTime) || !Precision.AlmostBigger(s.EndTime, time)) return false; - return Precision.AlmostEquals((time - s.StartTime) % s.SpanDuration, 0); + return nodeIndexFromTime(s, time - hitObject.StartTime) != -1; }); if (sampleObj == null) return null; IList samples; if (sampleObj is Slider slider) - samples = slider.NodeSamples[(int)Math.Round((time - slider.StartTime) % slider.SpanDuration)]; + samples = slider.NodeSamples[nodeIndexFromTime(slider, time - slider.StartTime)]; else samples = sampleObj.Samples; return samples; } + /// + /// Get the repeat node at a point in time. + /// + /// The slider. + /// The time since the start time of the slider. + /// Index of the node. -1 if there isn't a node at the specific time. + private int nodeIndexFromTime(IHasPathWithRepeats curve, double timeSinceStart) + { + double spanDuration = curve.Duration / curve.SpanCount(); + double nodeIndex = timeSinceStart / spanDuration; + + if (Precision.AlmostEquals(nodeIndex - Math.Round(nodeIndex), 0)) + return (int)Math.Round(nodeIndex); + + return -1; + } + private bool isOverlappingWithRecent(IReadOnlyList hitObjects, int idx) { var target = hitObjects[idx]; From 58b439b728c962c858f1cacbd817d6b5cb4020b4 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 10:38:21 +0800 Subject: [PATCH 1010/1621] Switch to `IHasPathWithRepeats` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 31fb70e9c0..54e6708bb8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -431,8 +431,8 @@ namespace osu.Game.Rulesets.Osu.Mods IList samples; - if (sampleObj is Slider slider) - samples = slider.NodeSamples[nodeIndexFromTime(slider, time - slider.StartTime)]; + if (sampleObj is IHasPathWithRepeats slider) + samples = slider.NodeSamples[nodeIndexFromTime(slider, time - sampleObj.StartTime)]; else samples = sampleObj.Samples; From 812624a502cdff0cd276d53857f83516fc9d07da Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 24 Jun 2021 10:45:20 +0800 Subject: [PATCH 1011/1621] use `.Equals()` instead --- osu.Game/Overlays/Chat/ChatLine.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 987dc27802..01cfe9a55b 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -240,7 +240,7 @@ namespace osu.Game.Overlays.Chat { get { - if (sender == User.SYSTEM_USER) + if (sender.Equals(User.SYSTEM_USER)) return Array.Empty(); List items = new List @@ -248,7 +248,7 @@ namespace osu.Game.Overlays.Chat new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action) }; - if (sender.Id != api.LocalUser.Value.Id) + if (!sender.Equals(api.LocalUser.Value)) items.Add(new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction)); return items.ToArray(); From 04510f1acee717847765b22edbba7dc2b63e416d Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 10:54:21 +0800 Subject: [PATCH 1012/1621] Removed odd-looking decrement and checks in `addHitSamples` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 25 ++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 54e6708bb8..f2b70b19a8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -217,29 +217,32 @@ namespace osu.Game.Rulesets.Osu.Mods { var lastSampleIdx = 0; - foreach (var x in hitObjects) + foreach (var obj in hitObjects) { - var samples = getSamplesAtTime(origHitObjects, x.StartTime); + var samples = getSamplesAtTime(origHitObjects, obj.StartTime); if (samples == null) { - while (lastSampleIdx < origHitObjects.Count && origHitObjects[lastSampleIdx].StartTime <= x.StartTime) + // If samples aren't available at the exact start time of the object, + // use samples (without additions) in the closest original hit object instead + + while (lastSampleIdx < origHitObjects.Count && origHitObjects[lastSampleIdx].StartTime <= obj.StartTime) lastSampleIdx++; - lastSampleIdx--; - if (lastSampleIdx < 0 && lastSampleIdx >= origHitObjects.Count) continue; + if (lastSampleIdx >= origHitObjects.Count) continue; - if (lastSampleIdx < origHitObjects.Count - 1) + if (lastSampleIdx > 0) { - // get samples from the next hit object if it is closer in time - if (origHitObjects[lastSampleIdx + 1].StartTime - x.StartTime < x.StartTime - origHitObjects[lastSampleIdx].StartTime) - lastSampleIdx++; + // get samples from the previous hit object if it is closer in time + if (obj.StartTime - origHitObjects[lastSampleIdx - 1].StartTime < origHitObjects[lastSampleIdx].StartTime - obj.StartTime) + lastSampleIdx--; } - x.Samples = origHitObjects[lastSampleIdx].Samples.Where(s => !HitSampleInfo.AllAdditions.Contains(s.Name)).ToList(); + // Remove additions + obj.Samples = origHitObjects[lastSampleIdx].Samples.Where(s => !HitSampleInfo.AllAdditions.Contains(s.Name)).ToList(); } else - x.Samples = samples; + obj.Samples = samples; } } From 6202eed5e24d903e18834efb2801aa4a5d2e42b9 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 10:56:14 +0800 Subject: [PATCH 1013/1621] Moved a misplaced `!` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index f2b70b19a8..796abf30f4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Osu.Mods .Where(timingPoint => Precision.AlmostBigger(endTime, timingPoint.Time)) .SelectMany(timingPoint => getBeatsForTimingPoint(timingPoint, endTime)) .Where(beat => Precision.AlmostBigger(beat, startTime)) - .Where(beat => isInsideBreakPeriod(beatmap.Breaks, beat)) + .Where(beat => !isInsideBreakPeriod(beatmap.Breaks, beat)) .ToList(); // Remove beats that are too close to the next one (e.g. due to timing point changes) @@ -207,7 +207,8 @@ namespace osu.Game.Rulesets.Osu.Mods { var beat = beats[i]; - if (Precision.AlmostBigger(beatmap.ControlPointInfo.TimingPointAt(beat).BeatLength / 2, beats[i + 1] - beat)) beats.RemoveAt(i); + if (Precision.AlmostBigger(beatmap.ControlPointInfo.TimingPointAt(beat).BeatLength / 2, beats[i + 1] - beat)) + beats.RemoveAt(i); } return beats; @@ -381,7 +382,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// The time to be checked.= private bool isInsideBreakPeriod(IEnumerable breaks, double time) { - return !breaks.Any(breakPeriod => + return breaks.Any(breakPeriod => { var firstObjAfterBreak = origHitObjects.First(obj => Precision.AlmostBigger(obj.StartTime, breakPeriod.EndTime)); From 6fca8ba5b07a55ebc97326812ee1548f3c0be9ab Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 11:21:43 +0800 Subject: [PATCH 1014/1621] Better explanation for `fixComboInfo` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 796abf30f4..0c6097bc7e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -249,13 +249,20 @@ namespace osu.Game.Rulesets.Osu.Mods private void fixComboInfo(List hitObjects) { - // First follow the combo indices in the original beatmap - hitObjects.ForEach(x => + // Copy combo indices from the closest preceding object + hitObjects.ForEach(obj => { - var origObj = origHitObjects.FindLast(y => Precision.AlmostBigger(x.StartTime, y.StartTime)); - x.ComboIndex = origObj?.ComboIndex ?? 0; + var closestOrigObj = origHitObjects.FindLast(y => Precision.AlmostBigger(obj.StartTime, y.StartTime)); + + // It shouldn't be possible for origObj to be null + // But if it is, obj should be in the first combo + obj.ComboIndex = closestOrigObj?.ComboIndex ?? 0; }); - // Then reprocess them to ensure continuity in the combo indices and add indices in current combo + + // The copied combo indices may not be continuous if the original map starts and ends a combo in between beats + // e.g. A stream with each object starting a new combo + // So combo indices need to be reprocessed to ensure continuity + // Other kinds of combo info are also added in the process var combos = hitObjects.GroupBy(x => x.ComboIndex).ToList(); for (var i = 0; i < combos.Count; i++) From f74275a3b5a676fe2f56f1d974ee41b12445d982 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 11:29:10 +0800 Subject: [PATCH 1015/1621] Moved RNG initialisation to a better place --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 0c6097bc7e..dfce423399 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -93,6 +93,8 @@ namespace osu.Game.Rulesets.Osu.Mods private List origHitObjects; + private Random rng; + #endregion #region Sudden Death (IApplicableFailOverride) @@ -161,6 +163,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override void ApplyToBeatmap(IBeatmap beatmap) { Seed.Value ??= RNG.Next(); + rng = new Random(Seed.Value.GetValueOrDefault()); var osuBeatmap = (OsuBeatmap)beatmap; @@ -284,8 +287,6 @@ namespace osu.Game.Rulesets.Osu.Mods { if (hitObjects.Count == 0) return; - var rng = new Random(Seed.Value.GetValueOrDefault()); - float nextSingle(float max = 1f) => (float)(rng.NextDouble() * max); var direction = MathHelper.TwoPi * nextSingle(); From 71b5ed16c0e5182f1555e3e6cfa739b4d74742d4 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 11:37:00 +0800 Subject: [PATCH 1016/1621] Avoid using osuTK constants; Use `MathF` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index dfce423399..8bbc6f4703 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -289,7 +289,9 @@ namespace osu.Game.Rulesets.Osu.Mods float nextSingle(float max = 1f) => (float)(rng.NextDouble() * max); - var direction = MathHelper.TwoPi * nextSingle(); + const float two_pi = MathF.PI * 2; + + var direction = two_pi * nextSingle(); var maxComboIndex = hitObjects.Last().ComboIndex; for (var i = 0; i < hitObjects.Count; i++) @@ -312,14 +314,14 @@ namespace osu.Game.Rulesets.Osu.Mods do { - if (tryCount > 0) direction = MathHelper.TwoPi * nextSingle(); + if (tryCount > 0) direction = two_pi * nextSingle(); var relativePos = new Vector2( - distance * (float)Math.Cos(direction), - distance * (float)Math.Sin(direction) + distance * MathF.Cos(direction), + distance * MathF.Sin(direction) ); relativePos = getRotatedVector(lastPos, relativePos); - direction = (float)Math.Atan2(relativePos.Y, relativePos.X); + direction = MathF.Atan2(relativePos.Y, relativePos.X); var newPosition = Vector2.Add(lastPos, relativePos); @@ -332,9 +334,9 @@ namespace osu.Game.Rulesets.Osu.Mods } while (distance >= obj.Radius * 2 && isOverlappingWithRecent(hitObjects, i)); if (obj.LastInCombo) - direction = MathHelper.TwoPi * nextSingle(); + direction = two_pi * nextSingle(); else - direction += distance / distance_cap * (nextSingle() * MathHelper.TwoPi - MathHelper.Pi); + direction += distance / distance_cap * (nextSingle() * two_pi - MathF.PI); } } From f8fe4ab4828f72cc71bddebe58fe6890d683d906 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 12:02:48 +0800 Subject: [PATCH 1017/1621] Refactor and rename `isOverlappingWithRecent` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 8bbc6f4703..8a9f9bf224 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -331,7 +331,7 @@ namespace osu.Game.Rulesets.Osu.Mods tryCount++; if (tryCount % 10 == 0) distance *= 0.9f; - } while (distance >= obj.Radius * 2 && isOverlappingWithRecent(hitObjects, i)); + } while (distance >= obj.Radius * 2 && checkForOverlap(hitObjects.SkipLast(hitObjects.Count - i).TakeLast(overlap_check_count), obj)); if (obj.LastInCombo) direction = two_pi * nextSingle(); @@ -470,11 +470,9 @@ namespace osu.Game.Rulesets.Osu.Mods return -1; } - private bool isOverlappingWithRecent(IReadOnlyList hitObjects, int idx) + private bool checkForOverlap(IEnumerable objectsToCheck, OsuHitObject target) { - var target = hitObjects[idx]; - return hitObjects.SkipLast(hitObjects.Count - idx).TakeLast(overlap_check_count) - .Any(h => Vector2.Distance(h.Position, target.Position) < target.Radius * 2); + return objectsToCheck.Any(h => Vector2.Distance(h.Position, target.Position) < target.Radius * 2); } /// From 877c775e35069ee02bba3a6120036ad602c576da Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 12:20:23 +0800 Subject: [PATCH 1018/1621] Added comments --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 8a9f9bf224..1ec08b2c40 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -173,11 +173,11 @@ namespace osu.Game.Rulesets.Osu.Mods origHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); var hitObjects = generateBeats(osuBeatmap) - .Select(x => + .Select(beat => { var newCircle = new HitCircle(); newCircle.ApplyDefaults(controlPointInfo, osuBeatmap.BeatmapInfo.BaseDifficulty); - newCircle.StartTime = x; + newCircle.StartTime = beat; return (OsuHitObject)newCircle; }).ToList(); @@ -199,9 +199,13 @@ namespace osu.Game.Rulesets.Osu.Mods var endTime = endObj.GetEndTime(); var beats = beatmap.ControlPointInfo.TimingPoints + // Ignore timing points after endTime .Where(timingPoint => Precision.AlmostBigger(endTime, timingPoint.Time)) + // Generate the beats .SelectMany(timingPoint => getBeatsForTimingPoint(timingPoint, endTime)) + // Remove beats before startTime .Where(beat => Precision.AlmostBigger(beat, startTime)) + // Remove beats during breaks .Where(beat => !isInsideBreakPeriod(beatmap.Breaks, beat)) .ToList(); @@ -320,6 +324,7 @@ namespace osu.Game.Rulesets.Osu.Mods distance * MathF.Cos(direction), distance * MathF.Sin(direction) ); + // Rotate the new circle away from playfield border relativePos = getRotatedVector(lastPos, relativePos); direction = MathF.Atan2(relativePos.Y, relativePos.X); @@ -428,6 +433,9 @@ namespace osu.Game.Rulesets.Osu.Mods /// Hit samples private IList getSamplesAtTime(IEnumerable hitObjects, double time) { + // Get a hit object that + // either has StartTime equal to the target time + // or has a repeat node at the target time var sampleObj = hitObjects.FirstOrDefault(hitObject => { if (Precision.AlmostEquals(time, hitObject.StartTime)) @@ -550,6 +558,10 @@ namespace osu.Game.Rulesets.Osu.Mods ); } + /// + /// Move the hit object into playfield, taking its radius into account. + /// + /// The hit object to be clamped. private void clampToPlayfield(OsuHitObject obj) { var position = obj.Position; From cd6f17537531203eaa7f1782e15c73ba520f5e11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Jun 2021 13:29:06 +0900 Subject: [PATCH 1019/1621] Ensure beatmap is reloaded before each playlist room test run --- .../Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index c4da2ab48a..6d7a254ab9 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -40,8 +40,6 @@ namespace osu.Game.Tests.Visual.Playlists Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait(); - ((DummyAPIAccess)API).HandleRequest = req => { switch (req) @@ -58,6 +56,7 @@ namespace osu.Game.Tests.Visual.Playlists [SetUpSteps] public void SetupSteps() { + AddStep("ensure has beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait()); AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(Room))); AddUntilStep("wait for load", () => match.IsCurrentScreen()); } From 6629f8706a741b67224423f25b473bc067e11047 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 12:31:25 +0800 Subject: [PATCH 1020/1621] Directly fade to gray instead of computing the color values --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 1ec08b2c40..9734b17ce0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -29,7 +29,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods { @@ -146,8 +145,7 @@ namespace osu.Game.Rulesets.Osu.Mods var colour = drawable.Colour; - var avgColour = colour.AverageColour.Linear; - drawable.FadeColour(new Color4(avgColour.R * 0.45f, avgColour.G * 0.45f, avgColour.B * 0.45f, avgColour.A)) + drawable.FadeColour(OsuColour.Gray(0.45f)) .Then().Delay(h.TimePreempt - controlPointInfo.TimingPointAt(h.StartTime).BeatLength - undim_duration) .FadeColour(colour, undim_duration); From 2268d7f8a586f3121c420e2f04ceaf9bcd2a05b1 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 13:19:42 +0800 Subject: [PATCH 1021/1621] Extract utility methods into helper class; Better xmldoc and naming --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 79 +--------------- osu.Game.Rulesets.Osu/Utils/VectorHelper.cs | 100 ++++++++++++++++++++ 2 files changed, 102 insertions(+), 77 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Utils/VectorHelper.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 97e3d82664..bcd5274e02 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.Utils; using osuTK; namespace osu.Game.Rulesets.Osu.Mods @@ -23,15 +24,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Description => "It never gets boring!"; - // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. - // The closer the hit objects draw to the border, the sharper the turn - private const float playfield_edge_ratio = 0.375f; - - private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio; - private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio; - - private static readonly Vector2 playfield_middle = OsuPlayfield.BASE_SIZE / 2; - private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; private Random rng; @@ -113,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Mods distanceToPrev * (float)Math.Sin(current.AngleRad) ); - posRelativeToPrev = getRotatedVector(previous.EndPositionRandomised, posRelativeToPrev); + posRelativeToPrev = VectorHelper.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); @@ -185,73 +177,6 @@ namespace osu.Game.Rulesets.Osu.Mods } } - /// - /// Determines the position of the current hit object relative to the previous one. - /// - /// The position of the current hit object relative to the previous one - private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev) - { - var relativeRotationDistance = 0f; - - if (prevPosChanged.X < playfield_middle.X) - { - relativeRotationDistance = Math.Max( - (border_distance_x - prevPosChanged.X) / border_distance_x, - relativeRotationDistance - ); - } - else - { - relativeRotationDistance = Math.Max( - (prevPosChanged.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x, - relativeRotationDistance - ); - } - - if (prevPosChanged.Y < playfield_middle.Y) - { - relativeRotationDistance = Math.Max( - (border_distance_y - prevPosChanged.Y) / border_distance_y, - relativeRotationDistance - ); - } - else - { - relativeRotationDistance = Math.Max( - (prevPosChanged.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y, - relativeRotationDistance - ); - } - - return rotateVectorTowardsVector(posRelativeToPrev, playfield_middle - prevPosChanged, relativeRotationDistance / 2); - } - - /// - /// Rotates vector "initial" towards vector "destinantion" - /// - /// Vector to rotate to "destination" - /// Vector "initial" should be rotated to - /// The angle the vector should be rotated relative to the difference between the angles of the the two vectors. - /// Resulting vector - private Vector2 rotateVectorTowardsVector(Vector2 initial, Vector2 destination, float relativeDistance) - { - var initialAngleRad = Math.Atan2(initial.Y, initial.X); - var destAngleRad = Math.Atan2(destination.Y, destination.X); - - var diff = destAngleRad - initialAngleRad; - - while (diff < -Math.PI) diff += 2 * Math.PI; - - while (diff > Math.PI) diff -= 2 * Math.PI; - - var finalAngleRad = initialAngleRad + relativeDistance * diff; - - return new Vector2( - initial.Length * (float)Math.Cos(finalAngleRad), - initial.Length * (float)Math.Sin(finalAngleRad) - ); - } - private class RandomObjectInfo { public float AngleRad { get; set; } diff --git a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs b/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs new file mode 100644 index 0000000000..bc482a3dd7 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs @@ -0,0 +1,100 @@ +// 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.Game.Rulesets.Osu.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Utils +{ + public static class VectorHelper + { + // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. + // The closer the hit objects draw to the border, the sharper the turn + private const float playfield_edge_ratio = 0.375f; + + private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio; + private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio; + + private static readonly Vector2 playfield_middle = OsuPlayfield.BASE_SIZE / 2; + + /// + /// Rotate a hit object away from the playfield edge, while keeping a constant distance + /// from the previous object. + /// + /// + /// The extent of rotation depends on the position of the hit object. Hit objects + /// closer to the playfield edge will be rotated to a larger extent. + /// + /// Position of the previous hit object. + /// Position of the hit object to be rotated, relative to the previous hit object. + /// + /// The extent of rotation. + /// 0 means the hit object is never rotated. + /// 1 means the hit object will be fully rotated towards playfield center when it is originally at playfield edge. + /// + /// The new position of the hit object, relative to the previous one. + public static Vector2 RotateAwayFromEdge(Vector2 prevObjectPos, Vector2 posRelativeToPrev, float rotationRatio = 0.5f) + { + var relativeRotationDistance = 0f; + + if (prevObjectPos.X < playfield_middle.X) + { + relativeRotationDistance = Math.Max( + (border_distance_x - prevObjectPos.X) / border_distance_x, + relativeRotationDistance + ); + } + else + { + relativeRotationDistance = Math.Max( + (prevObjectPos.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x, + relativeRotationDistance + ); + } + + if (prevObjectPos.Y < playfield_middle.Y) + { + relativeRotationDistance = Math.Max( + (border_distance_y - prevObjectPos.Y) / border_distance_y, + relativeRotationDistance + ); + } + else + { + relativeRotationDistance = Math.Max( + (prevObjectPos.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y, + relativeRotationDistance + ); + } + + return RotateVectorTowardsVector(posRelativeToPrev, playfield_middle - prevObjectPos, relativeRotationDistance * rotationRatio); + } + + /// + /// Rotates vector "initial" towards vector "destination". + /// + /// The vector to be rotated. + /// The vector that "initial" should be rotated towards. + /// How much "initial" should be rotated. 0 means no rotation. 1 means "initial" is fully rotated to equal "destination". + /// The rotated vector. + public static Vector2 RotateVectorTowardsVector(Vector2 initial, Vector2 destination, float rotationRatio) + { + var initialAngleRad = Math.Atan2(initial.Y, initial.X); + var destAngleRad = Math.Atan2(destination.Y, destination.X); + + var diff = destAngleRad - initialAngleRad; + + while (diff < -Math.PI) diff += 2 * Math.PI; + + while (diff > Math.PI) diff -= 2 * Math.PI; + + var finalAngleRad = initialAngleRad + rotationRatio * diff; + + return new Vector2( + initial.Length * (float)Math.Cos(finalAngleRad), + initial.Length * (float)Math.Sin(finalAngleRad) + ); + } + } +} From 153e204d200bf554b19236c5fc283982047e148c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 13:22:10 +0800 Subject: [PATCH 1022/1621] Cap rotation ratio to 1 --- osu.Game.Rulesets.Osu/Utils/VectorHelper.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs b/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs index bc482a3dd7..e3fe3249e8 100644 --- a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs +++ b/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs @@ -68,7 +68,11 @@ namespace osu.Game.Rulesets.Osu.Utils ); } - return RotateVectorTowardsVector(posRelativeToPrev, playfield_middle - prevObjectPos, relativeRotationDistance * rotationRatio); + return RotateVectorTowardsVector( + posRelativeToPrev, + playfield_middle - prevObjectPos, + Math.Min(1, relativeRotationDistance * rotationRatio) + ); } /// From 63ab40ec24c24d9221f942f12cd2d60a3365bd42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Jun 2021 14:37:26 +0900 Subject: [PATCH 1023/1621] Fix potential deadlocking behaviour (and convert `ResetEvent` to `Semaphore`) --- osu.Game/Database/RealmContextFactory.cs | 71 ++++++++++++++---------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index ed5931dd2b..4d81f8676f 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -26,6 +26,11 @@ namespace osu.Game.Database /// private readonly object writeLock = new object(); + /// + /// Lock object which is held during sections. + /// + private readonly SemaphoreSlim blockingLock = new SemaphoreSlim(1); + 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"); @@ -33,8 +38,6 @@ namespace osu.Game.Database 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; public Realm Context @@ -64,7 +67,7 @@ namespace osu.Game.Database public RealmUsage GetForRead() { reads.Value++; - return new RealmUsage(this); + return new RealmUsage(createContext()); } public RealmWriteUsage GetForWrite() @@ -73,8 +76,13 @@ namespace osu.Game.Database pending_writes.Value++; Monitor.Enter(writeLock); + return new RealmWriteUsage(createContext(), writeComplete); + } - return new RealmWriteUsage(this); + private void writeComplete() + { + Monitor.Exit(writeLock); + pending_writes.Value--; } protected override void Update() @@ -87,15 +95,22 @@ namespace osu.Game.Database private Realm createContext() { - blockingResetEvent.Wait(); - - contexts_created.Value++; - - return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) + try { - SchemaVersion = schema_version, - MigrationCallback = onMigration, - }); + blockingLock.Wait(); + + contexts_created.Value++; + + return Realm.GetInstance(new RealmConfiguration(storage.GetFullPath($"{database_name}.realm", true)) + { + SchemaVersion = schema_version, + MigrationCallback = onMigration, + }); + } + finally + { + blockingLock.Release(); + } } private void onMigration(Migration migration, ulong lastSchemaVersion) @@ -113,21 +128,23 @@ namespace osu.Game.Database { base.Dispose(isDisposing); - BlockAllOperations(); + // In the standard case, operations will already be blocked by the Update thread "pausing" from GameHost exit. + // This avoids waiting (forever) on an already entered semaphore. + if (context != null || active_usages.Value > 0) + BlockAllOperations(); + + blockingLock?.Dispose(); } public IDisposable BlockAllOperations() { - blockingResetEvent.Reset(); + blockingLock.Wait(); flushContexts(); - return new InvokeOnDisposal(this, r => endBlockingSection()); + return new InvokeOnDisposal(this, endBlockingSection); } - private void endBlockingSection() - { - blockingResetEvent.Set(); - } + private static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); private void flushContexts() { @@ -148,13 +165,10 @@ namespace osu.Game.Database { public readonly Realm Realm; - protected readonly RealmContextFactory Factory; - - internal RealmUsage(RealmContextFactory factory) + internal RealmUsage(Realm context) { active_usages.Value++; - Factory = factory; - Realm = factory.createContext(); + Realm = context; } /// @@ -172,11 +186,13 @@ namespace osu.Game.Database /// public class RealmWriteUsage : RealmUsage { + private readonly Action onWriteComplete; private readonly Transaction transaction; - internal RealmWriteUsage(RealmContextFactory factory) - : base(factory) + internal RealmWriteUsage(Realm context, Action onWriteComplete) + : base(context) { + this.onWriteComplete = onWriteComplete; transaction = Realm.BeginWrite(); } @@ -200,8 +216,7 @@ namespace osu.Game.Database base.Dispose(); - Monitor.Exit(Factory.writeLock); - pending_writes.Value--; + onWriteComplete(); } } } From be55c7e075ad103bedd11dedf942ed193c731ea4 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 13:39:50 +0800 Subject: [PATCH 1024/1621] Minor fixes in comments --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 9734b17ce0..00caea0bfe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Mods .Then().Delay(h.TimePreempt - controlPointInfo.TimingPointAt(h.StartTime).BeatLength - undim_duration) .FadeColour(colour, undim_duration); - // remove approach circles + // Remove approach circles circle.ApproachCircle.Hide(); } } @@ -239,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (lastSampleIdx > 0) { - // get samples from the previous hit object if it is closer in time + // Get samples from the previous hit object if it is closer in time if (obj.StartTime - origHitObjects[lastSampleIdx - 1].StartTime < origHitObjects[lastSampleIdx].StartTime - obj.StartTime) lastSampleIdx--; } @@ -259,7 +259,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var closestOrigObj = origHitObjects.FindLast(y => Precision.AlmostBigger(obj.StartTime, y.StartTime)); - // It shouldn't be possible for origObj to be null + // It shouldn't be possible for closestOrigObj to be null // But if it is, obj should be in the first combo obj.ComboIndex = closestOrigObj?.ComboIndex ?? 0; }); From 27735eeedba9d3947ff403d1db12924f789b13ed Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Thu, 24 Jun 2021 13:45:38 +0800 Subject: [PATCH 1025/1621] fixed code --- .../BeatmapListingFilterControl.cs | 23 ++++++++----- osu.Game/Overlays/BeatmapListingOverlay.cs | 34 +++++++------------ 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index f49d913bb2..b6a0846407 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -25,8 +25,9 @@ namespace osu.Game.Overlays.BeatmapListing public class BeatmapListingFilterControl : CompositeDrawable { /// - /// Fired when a search finishes. Contains only new items in the case of pagination. - /// Fired with BeatmapListingSearchControl when non-supporter user used supporter-only filters. + /// Fired when a search finishes. + /// SearchFinished.Type = ResultsReturned when results returned. Contains only new items in the case of pagination. + /// SearchFinished.Type = SupporterOnlyFilter when a non-supporter user applied supporter-only filters. /// public Action SearchFinished; @@ -216,7 +217,7 @@ namespace osu.Game.Overlays.BeatmapListing getSetsRequest = null; // check if an non-supporter user used supporter-only filters - if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) + if (!api.LocalUser.Value.IsSupporter) { List filters = new List(); @@ -226,12 +227,14 @@ namespace osu.Game.Overlays.BeatmapListing if (searchControl.Ranks.Any()) filters.Add(BeatmapsStrings.ListingSearchFiltersRank); - SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); - } - else - { - SearchFinished?.Invoke(SearchResult.ResultsReturned(sets)); + if (filters.Any()) + { + SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); + return; + } } + + SearchFinished?.Invoke(SearchResult.ResultsReturned(sets)); }; api.Queue(getSetsRequest); @@ -259,10 +262,14 @@ namespace osu.Game.Overlays.BeatmapListing public enum SearchResultType { + // returned with Results ResultsReturned, + // non-supporter user applied supporter-only filters SupporterOnlyFilter } + // Results only valid when Type == ResultsReturned + // Filters only valid when Type == SupporterOnlyFilter public struct SearchResult { public SearchResultType Type { get; private set; } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 63800e6585..c2ba3d5bc0 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -256,7 +256,7 @@ namespace osu.Game.Overlays // using string literals as there's no proper processing for LocalizeStrings yet public class SupporterRequiredDrawable : CompositeDrawable { - private OsuSpriteText filtersText; + private LinkFlowContainer supporterRequiredText; public SupporterRequiredDrawable() { @@ -275,7 +275,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Children = new[] + Children = new Drawable[] { new Sprite { @@ -285,39 +285,31 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - createSupporterText(), + supporterRequiredText = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 10 }, + }, } }); } public void UpdateText(List filters) { - // use string literals for now - filtersText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); - } + supporterRequiredText.Clear(); - private Drawable createSupporterText() - { - LinkFlowContainer supporterRequiredText = new LinkFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Bottom = 10 }, - }; - - filtersText = (OsuSpriteText)supporterRequiredText.AddText( - "_", + supporterRequiredText.AddText( + BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(), t => { t.Font = OsuFont.GetFont(size: 16); t.Colour = Colour4.White; } - ).First(); + ); supporterRequiredText.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), @"/store/products/supporter-tag"); - - return supporterRequiredText; } } From 16d58935352391052c4ffce4fcf98b875ef0dbca Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 24 Jun 2021 15:02:45 +0900 Subject: [PATCH 1026/1621] Add `DroppedObjectContainer` class --- .../TestSceneCatcher.cs | 6 +++--- .../TestSceneCatcherArea.cs | 8 ++------ .../TestSceneHyperDashColouring.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 6 +----- osu.Game.Rulesets.Catch/UI/Catcher.cs | 4 ++-- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 2 +- .../UI/DroppedObjectContainer.cs | 17 +++++++++++++++++ 7 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 900691ecae..28b1aaf03a 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Tests [Resolved] private OsuConfigManager config { get; set; } - private Container droppedObjectContainer; + private DroppedObjectContainer droppedObjectContainer; private TestCatcher catcher; @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Tests }; var trailContainer = new Container(); - droppedObjectContainer = new Container(); + droppedObjectContainer = new DroppedObjectContainer(); catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty); Child = new Container @@ -293,7 +293,7 @@ namespace osu.Game.Rulesets.Catch.Tests { public IEnumerable CaughtObjects => this.ChildrenOfType(); - public TestCatcher(Container trailsTarget, Container droppedObjectTarget, BeatmapDifficulty difficulty) + public TestCatcher(Container trailsTarget, DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty) : base(trailsTarget, droppedObjectTarget, difficulty) { } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 4af5098451..dca75eee14 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Framework.Utils; @@ -97,10 +96,7 @@ namespace osu.Game.Rulesets.Catch.Tests SetContents(_ => { - var droppedObjectContainer = new Container - { - RelativeSizeAxes = Axes.Both - }; + var droppedObjectContainer = new DroppedObjectContainer(); return new CatchInputManager(catchRuleset) { @@ -126,7 +122,7 @@ namespace osu.Game.Rulesets.Catch.Tests private class TestCatcherArea : CatcherArea { - public TestCatcherArea(Container droppedObjectContainer, BeatmapDifficulty beatmapDifficulty) + public TestCatcherArea(DroppedObjectContainer droppedObjectContainer, BeatmapDifficulty beatmapDifficulty) : base(droppedObjectContainer, beatmapDifficulty) { } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 683a776dcc..4af528ef22 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("create hyper-dashing catcher", () => { - Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container()) + Child = setupSkinHierarchy(catcherArea = new CatcherArea(new DroppedObjectContainer()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 644facdabc..21501398fc 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; @@ -35,10 +34,7 @@ namespace osu.Game.Rulesets.Catch.UI public CatchPlayfield(BeatmapDifficulty difficulty) { - var droppedObjectContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }; + var droppedObjectContainer = new DroppedObjectContainer(); CatcherArea = new CatcherArea(droppedObjectContainer, difficulty) { diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 1f01dbabb5..6b42e3ddd3 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Contains objects dropped from the plate. /// - private readonly Container droppedObjectTarget; + private readonly DroppedObjectContainer droppedObjectTarget; public CatcherAnimationState CurrentState { @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly DrawablePool caughtBananaPool; private readonly DrawablePool caughtDropletPool; - public Catcher([NotNull] Container trailsTarget, [NotNull] Container droppedObjectTarget, BeatmapDifficulty difficulty = null) + public Catcher([NotNull] Container trailsTarget, [NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null) { this.trailsTarget = trailsTarget; this.droppedObjectTarget = droppedObjectTarget; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index cdb15c2b4c..951a10fc01 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.UI /// private int currentDirection; - public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null) + public CatcherArea(DroppedObjectContainer droppedObjectContainer, BeatmapDifficulty difficulty = null) { Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); Children = new Drawable[] diff --git a/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs b/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs new file mode 100644 index 0000000000..b44b0caae4 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs @@ -0,0 +1,17 @@ +// 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.Catch.Objects.Drawables; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class DroppedObjectContainer : Container + { + public DroppedObjectContainer() + { + RelativeSizeAxes = Axes.Both; + } + } +} From 1a47bc254d4e98407ab7799823fdba3b1992a444 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 14:55:49 +0800 Subject: [PATCH 1027/1621] Increase acceptable difference for Precision calls --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 39 +++++++++++++++------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 00caea0bfe..bd3287634c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -84,6 +84,11 @@ namespace osu.Game.Rulesets.Osu.Mods /// private const double undim_duration = 96; + /// + /// Acceptable difference for timing comparisons + /// + private const double timing_precision = 1; + #endregion #region Private Fields @@ -198,11 +203,11 @@ namespace osu.Game.Rulesets.Osu.Mods var beats = beatmap.ControlPointInfo.TimingPoints // Ignore timing points after endTime - .Where(timingPoint => Precision.AlmostBigger(endTime, timingPoint.Time)) + .Where(timingPoint => almostBigger(endTime, timingPoint.Time)) // Generate the beats .SelectMany(timingPoint => getBeatsForTimingPoint(timingPoint, endTime)) // Remove beats before startTime - .Where(beat => Precision.AlmostBigger(beat, startTime)) + .Where(beat => almostBigger(beat, startTime)) // Remove beats during breaks .Where(beat => !isInsideBreakPeriod(beatmap.Breaks, beat)) .ToList(); @@ -212,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var beat = beats[i]; - if (Precision.AlmostBigger(beatmap.ControlPointInfo.TimingPointAt(beat).BeatLength / 2, beats[i + 1] - beat)) + if (almostBigger(beatmap.ControlPointInfo.TimingPointAt(beat).BeatLength / 2, beats[i + 1] - beat)) beats.RemoveAt(i); } @@ -257,7 +262,7 @@ namespace osu.Game.Rulesets.Osu.Mods // Copy combo indices from the closest preceding object hitObjects.ForEach(obj => { - var closestOrigObj = origHitObjects.FindLast(y => Precision.AlmostBigger(obj.StartTime, y.StartTime)); + var closestOrigObj = origHitObjects.FindLast(y => almostBigger(obj.StartTime, y.StartTime)); // It shouldn't be possible for closestOrigObj to be null // But if it is, obj should be in the first combo @@ -397,10 +402,10 @@ namespace osu.Game.Rulesets.Osu.Mods { return breaks.Any(breakPeriod => { - var firstObjAfterBreak = origHitObjects.First(obj => Precision.AlmostBigger(obj.StartTime, breakPeriod.EndTime)); + var firstObjAfterBreak = origHitObjects.First(obj => almostBigger(obj.StartTime, breakPeriod.EndTime)); - return Precision.AlmostBigger(time, breakPeriod.StartTime) - && Precision.AlmostBigger(firstObjAfterBreak.StartTime, time); + return almostBigger(time, breakPeriod.StartTime) + && almostBigger(firstObjAfterBreak.StartTime, time); }); } @@ -410,7 +415,7 @@ namespace osu.Game.Rulesets.Osu.Mods int i = 0; var currentTime = timingPoint.Time; - while (Precision.AlmostBigger(mapEndTime, currentTime) && controlPointInfo.TimingPointAt(currentTime) == timingPoint) + while (almostBigger(mapEndTime, currentTime) && controlPointInfo.TimingPointAt(currentTime) == timingPoint) { beats.Add(Math.Floor(currentTime)); i++; @@ -436,13 +441,13 @@ namespace osu.Game.Rulesets.Osu.Mods // or has a repeat node at the target time var sampleObj = hitObjects.FirstOrDefault(hitObject => { - if (Precision.AlmostEquals(time, hitObject.StartTime)) + if (almostEquals(time, hitObject.StartTime)) return true; if (!(hitObject is IHasPathWithRepeats s)) return false; - if (!Precision.AlmostBigger(time, hitObject.StartTime) - || !Precision.AlmostBigger(s.EndTime, time)) + if (!almostBigger(time, hitObject.StartTime) + || !almostBigger(s.EndTime, time)) return false; return nodeIndexFromTime(s, time - hitObject.StartTime) != -1; @@ -470,7 +475,7 @@ namespace osu.Game.Rulesets.Osu.Mods double spanDuration = curve.Duration / curve.SpanCount(); double nodeIndex = timeSinceStart / spanDuration; - if (Precision.AlmostEquals(nodeIndex - Math.Round(nodeIndex), 0)) + if (almostEquals(nodeIndex - Math.Round(nodeIndex), 0)) return (int)Math.Round(nodeIndex); return -1; @@ -583,6 +588,16 @@ namespace osu.Game.Rulesets.Osu.Mods return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow; } + private static bool almostBigger(double value1, double value2) + { + return Precision.AlmostBigger(value1, value2, timing_precision); + } + + private static bool almostEquals(double value1, double value2) + { + return Precision.AlmostEquals(value1, value2, timing_precision); + } + #endregion } } From c9ec4b9da46bb1692fa86a5650e8efe8bbedcb95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 00:28:39 +0900 Subject: [PATCH 1028/1621] Remove RoomTestScene inheritance from simple test scenes --- .../Multiplayer/TestSceneLoungeRoomInfo.cs | 34 +++---- .../TestSceneMatchBeatmapDetailArea.cs | 23 +++-- .../Multiplayer/TestSceneMatchHeader.cs | 71 +++++++------ .../Multiplayer/TestSceneMatchLeaderboard.cs | 99 ++++++++++--------- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestSceneMultiplayerRoomManager.cs | 2 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 32 +++--- .../TestScenePlaylistsParticipantsList.cs | 17 ++-- osu.Game/Tests/Visual/TestRoomContainer.cs | 38 +++++++ 9 files changed, 190 insertions(+), 128 deletions(-) create mode 100644 osu.Game/Tests/Visual/TestRoomContainer.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs index 9f24347ae9..a8cbd8b7ef 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs @@ -10,28 +10,28 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneLoungeRoomInfo : RoomTestScene + public class TestSceneLoungeRoomInfo : OsuTestScene { + private TestRoomContainer roomContainer; + [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Child = new RoomInfo + Child = roomContainer = new TestRoomContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 500 + Child = new RoomInfo + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500 + } }; }); - public override void SetUpSteps() - { - // Todo: Temp - } - [Test] public void TestNonSelectedRoom() { - AddStep("set null room", () => Room.RoomID.Value = null); + AddStep("set null room", () => roomContainer.Room.RoomID.Value = null); } [Test] @@ -39,11 +39,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set open room", () => { - Room.RoomID.Value = 0; - Room.Name.Value = "Room 0"; - Room.Host.Value = new User { Username = "peppy", Id = 2 }; - Room.EndDate.Value = DateTimeOffset.Now.AddMonths(1); - Room.Status.Value = new RoomStatusOpen(); + roomContainer.Room.RoomID.Value = 0; + roomContainer.Room.Name.Value = "Room 0"; + roomContainer.Room.Host.Value = new User { Username = "peppy", Id = 2 }; + roomContainer.Room.EndDate.Value = DateTimeOffset.Now.AddMonths(1); + roomContainer.Room.Status.Value = new RoomStatusOpen(); }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index 9ad9f2c883..2a254b1573 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchBeatmapDetailArea : RoomTestScene + public class TestSceneMatchBeatmapDetailArea : OsuTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -23,23 +23,28 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private RulesetStore rulesetStore { get; set; } + private TestRoomContainer roomContainer; + [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Child = new MatchBeatmapDetailArea + Child = roomContainer = new TestRoomContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500), - CreateNewItem = createNewItem + Child = new MatchBeatmapDetailArea + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500), + CreateNewItem = createNewItem + } }; }); private void createNewItem() { - Room.Playlist.Add(new PlaylistItem + roomContainer.Room.Playlist.Add(new PlaylistItem { - ID = Room.Playlist.Count, + ID = roomContainer.Room.Playlist.Count, Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, RequiredMods = diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 7cdc6b1a7d..50e6426472 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -11,42 +11,51 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchHeader : RoomTestScene + public class TestSceneMatchHeader : OsuTestScene { - public TestSceneMatchHeader() - { - Child = new Header(); - } + private TestRoomContainer roomContainer; [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Room.Playlist.Add(new PlaylistItem + Child = roomContainer = new TestRoomContainer { - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Title = "Title", - Artist = "Artist", - AuthorString = "Author", - }, - Version = "Version", - Ruleset = new OsuRuleset().RulesetInfo - } - }, - RequiredMods = - { - new OsuModDoubleTime(), - new OsuModNoFail(), - new OsuModRelax(), - } - }); - - Room.Name.Value = "A very awesome room"; - Room.Host.Value = new User { Id = 2, Username = "peppy" }; + Child = new Header() + }; }); + + [Test] + public void TestBasicRoom() + { + AddStep("set basic room", () => + { + roomContainer.Room.Playlist.Add(new PlaylistItem + { + Beatmap = + { + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Title = "Title", + Artist = "Artist", + AuthorString = "Author", + }, + Version = "Version", + Ruleset = new OsuRuleset().RulesetInfo + } + }, + RequiredMods = + { + new OsuModDoubleTime(), + new OsuModNoFail(), + new OsuModRelax(), + } + }); + + roomContainer.Room.Name.Value = "A very awesome room"; + roomContainer.Room.Host.Value = new User { Id = 2, Username = "peppy" }; + }); + } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 64eaf0556b..566dc9fc00 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -2,72 +2,75 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using Newtonsoft.Json; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Users; using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchLeaderboard : RoomTestScene + public class TestSceneMatchLeaderboard : OsuTestScene { - protected override bool UseOnlineAPI => true; - - public TestSceneMatchLeaderboard() - { - Add(new MatchLeaderboard - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(550f, 450f), - Scope = MatchLeaderboardScope.Overall, - }); - } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) + private void load() { - var req = new GetRoomScoresRequest(); - req.Success += v => { }; - req.Failure += _ => { }; + ((DummyAPIAccess)API).HandleRequest = r => + { + switch (r) + { + case GetRoomLeaderboardRequest leaderboardRequest: + leaderboardRequest.TriggerSuccess(new APILeaderboard + { + Leaderboard = new List + { + new APIUserScoreAggregate + { + UserID = 2, + User = new User { Id = 2, Username = "peppy" }, + TotalScore = 995533, + RoomID = 3, + CompletedBeatmaps = 1, + TotalAttempts = 6, + Accuracy = 0.9851 + }, + new APIUserScoreAggregate + { + UserID = 1040328, + User = new User { Id = 1040328, Username = "smoogipoo" }, + TotalScore = 981100, + RoomID = 3, + CompletedBeatmaps = 1, + TotalAttempts = 9, + Accuracy = 0.937 + } + } + }); + return true; + } - api.Queue(req); + return false; + }; } [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Room.RoomID.Value = 3; + Child = new TestRoomContainer + { + Room = { RoomID = { Value = 3 } }, + Child = new MatchLeaderboard + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(550f, 450f), + Scope = MatchLeaderboardScope.Overall, + } + }; }); - - private class GetRoomScoresRequest : APIRequest> - { - protected override string Target => "rooms/3/leaderboard"; - } - - private class RoomScore - { - [JsonProperty("user")] - public User User { get; set; } - - [JsonProperty("accuracy")] - public double Accuracy { get; set; } - - [JsonProperty("total_score")] - public int TotalScore { get; set; } - - [JsonProperty("pp")] - public double PP { get; set; } - - [JsonProperty("attempts")] - public int TotalAttempts { get; set; } - - [JsonProperty("completed")] - public int CompletedAttempts { get; set; } - } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 5b059c06f5..2725ef5976 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -29,7 +29,7 @@ using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerMatchSongSelect : RoomTestScene + public class TestSceneMultiplayerMatchSongSelect : ScreenTestScene { private BeatmapManager manager; private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index c008771fd9..80e36916b1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -12,7 +12,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Multiplayer { [HeadlessTest] - public class TestSceneMultiplayerRoomManager : RoomTestScene + public class TestSceneMultiplayerRoomManager : OsuTestScene { private TestMultiplayerRoomContainer roomContainer; private TestMultiplayerRoomManager roomManager => roomContainer.RoomManager; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 44a79b6598..3055a2c6c7 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -15,23 +15,25 @@ using osu.Game.Screens.OnlinePlay.Playlists; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsMatchSettingsOverlay : RoomTestScene + public class TestScenePlaylistsMatchSettingsOverlay : OsuTestScene { [Cached(Type = typeof(IRoomManager))] private TestRoomManager roomManager = new TestRoomManager(); + private TestRoomContainer roomContainer; private TestRoomSettings settings; [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - settings = new TestRoomSettings + Child = roomContainer = new TestRoomContainer { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible } + Child = settings = new TestRoomSettings + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible } + } }; - - Child = settings; }); [Test] @@ -39,19 +41,19 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("clear name and beatmap", () => { - Room.Name.Value = ""; - Room.Playlist.Clear(); + roomContainer.Room.Name.Value = ""; + roomContainer.Room.Playlist.Clear(); }); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set name", () => Room.Name.Value = "Room name"); + AddStep("set name", () => roomContainer.Room.Name.Value = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); + AddStep("set beatmap", () => roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); - AddStep("clear name", () => Room.Name.Value = ""); + AddStep("clear name", () => roomContainer.Room.Name.Value = ""); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); } @@ -67,7 +69,7 @@ namespace osu.Game.Tests.Visual.Playlists { settings.NameField.Current.Value = expected_name; settings.DurationField.Current.Value = expectedDuration; - Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); roomManager.CreateRequested = r => { @@ -88,8 +90,8 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("setup", () => { - Room.Name.Value = "Test Room"; - Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + roomContainer.Room.Name.Value = "Test Room"; + roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); fail = true; roomManager.CreateRequested = _ => !fail; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 255f147ec9..083930698f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -8,16 +8,21 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsParticipantsList : RoomTestScene + public class TestScenePlaylistsParticipantsList : OsuTestScene { + private TestRoomContainer roomContainer; + [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Room.RoomID.Value = 7; + Child = roomContainer = new TestRoomContainer + { + Room = { RoomID = { Value = 7 } } + }; for (int i = 0; i < 50; i++) { - Room.RecentParticipants.Add(new User + roomContainer.Room.RecentParticipants.Add(new User { Username = "peppy", Statistics = new UserStatistics { GlobalRank = 1234 }, @@ -31,7 +36,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(Direction.Horizontal) + roomContainer.Child = new ParticipantsDisplay(Direction.Horizontal) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -45,7 +50,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(Direction.Vertical) + roomContainer.Child = new ParticipantsDisplay(Direction.Vertical) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Tests/Visual/TestRoomContainer.cs b/osu.Game/Tests/Visual/TestRoomContainer.cs new file mode 100644 index 0000000000..c4af89cd51 --- /dev/null +++ b/osu.Game/Tests/Visual/TestRoomContainer.cs @@ -0,0 +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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Rooms; + +namespace osu.Game.Tests.Visual +{ + /// + /// Contains a that is resolvable by components in test scenes. + /// + public class TestRoomContainer : Container + { + /// + /// The cached . + /// + public readonly Room Room = new Room(); + + [Cached] + private readonly Bindable roomBindable; + + public TestRoomContainer() + { + RelativeSizeAxes = Axes.Both; + roomBindable = new Bindable(Room); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); + dependencies.Model.Value = Room; + return dependencies; + } + } +} From a98b5618b84e873adffeed23fdfd2a15a58a907c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 24 Jun 2021 10:07:38 +0300 Subject: [PATCH 1029/1621] Convert `RulesetResourcesSkin` to plain `ResourcesSkin` and pass non-null resources --- ...lesetResourcesSkin.cs => ResourcesSkin.cs} | 30 ++++++++----------- .../Skinning/RulesetSkinProvidingContainer.cs | 18 +++++++---- 2 files changed, 25 insertions(+), 23 deletions(-) rename osu.Game/Skinning/{RulesetResourcesSkin.cs => ResourcesSkin.cs} (61%) diff --git a/osu.Game/Skinning/RulesetResourcesSkin.cs b/osu.Game/Skinning/ResourcesSkin.cs similarity index 61% rename from osu.Game/Skinning/RulesetResourcesSkin.cs rename to osu.Game/Skinning/ResourcesSkin.cs index 5cf2eec338..3d17d7cc3d 100644 --- a/osu.Game/Skinning/RulesetResourcesSkin.cs +++ b/osu.Game/Skinning/ResourcesSkin.cs @@ -13,38 +13,32 @@ using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Audio; -using osu.Game.Rulesets; namespace osu.Game.Skinning { /// - /// An providing the resources of the ruleset for accessibility during lookups. + /// An that uses an underlying with namespaces for resources retrieval. /// - public class RulesetResourcesSkin : ISkin + public class ResourcesSkin : ISkin { - private readonly TextureStore? textures; - private readonly ISampleStore? samples; + private readonly TextureStore textures; + private readonly ISampleStore samples; - public RulesetResourcesSkin(Ruleset ruleset, GameHost host, AudioManager audio) + public ResourcesSkin(IResourceStore resources, GameHost host, AudioManager audio) { - IResourceStore? resources = ruleset.CreateResourceStore(); - - if (resources != null) - { - textures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); - samples = audio.GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); - } + textures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); + samples = audio.GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); } public Drawable? GetDrawableComponent(ISkinComponent component) => null; - public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => textures?.Get(componentName, wrapModeS, wrapModeT); + public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => textures.Get(componentName, wrapModeS, wrapModeT); public ISample? GetSample(ISampleInfo sampleInfo) { foreach (var lookup in sampleInfo.LookupNames) { - ISample? sample = samples?.Get(lookup); + ISample? sample = samples.Get(lookup); if (sample != null) return sample; } @@ -56,7 +50,7 @@ namespace osu.Game.Skinning #region Disposal - ~RulesetResourcesSkin() + ~ResourcesSkin() { // required to potentially clean up sample store from audio hierarchy. Dispose(false); @@ -77,8 +71,8 @@ namespace osu.Game.Skinning isDisposed = true; - textures?.Dispose(); - samples?.Dispose(); + textures.Dispose(); + samples.Dispose(); } #endregion diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 83e2d398f9..54bf91523f 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -87,11 +88,18 @@ namespace osu.Game.Skinning } } - var defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); - if (defaultSkinIndex >= 0) - SkinSources.Insert(defaultSkinIndex, new RulesetResourcesSkin(Ruleset, host, audio)); - else - SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio)); + if (Ruleset.CreateResourceStore() is IResourceStore resources) + { + int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + + if (defaultSkinIndex >= 0) + SkinSources.Insert(defaultSkinIndex, new ResourcesSkin(resources, host, audio)); + else + { + // Tests may potentially override the SkinManager with another source that doesn't include it in AllSources. + SkinSources.Add(new ResourcesSkin(resources, host, audio)); + } + } } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) From ae09c23e4e262d3f6cc815ead57aec7f734a1f60 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 24 Jun 2021 15:56:53 +0900 Subject: [PATCH 1030/1621] Resolve `DroppedObjectContainer` via DI --- .../TestSceneCatcher.cs | 38 +++++++++++-------- .../TestSceneCatcherArea.cs | 13 ++++--- .../TestSceneHyperDashColouring.cs | 18 +++++++-- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 9 +++-- osu.Game.Rulesets.Catch/UI/Catcher.cs | 6 +-- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 +- 6 files changed, 54 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 28b1aaf03a..1ad45d2f13 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -6,8 +6,8 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Game.Rulesets.Catch.UI; using osu.Framework.Graphics; +using osu.Game.Rulesets.Catch.UI; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; @@ -31,10 +31,23 @@ namespace osu.Game.Rulesets.Catch.Tests [Resolved] private OsuConfigManager config { get; set; } - private DroppedObjectContainer droppedObjectContainer; + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + private readonly Container trailContainer; private TestCatcher catcher; + public TestSceneCatcher() + { + Add(trailContainer = new Container + { + Anchor = Anchor.Centre, + Depth = -1 + }); + Add(droppedObjectContainer = new DroppedObjectContainer()); + } + [SetUp] public void SetUp() => Schedule(() => { @@ -43,20 +56,13 @@ namespace osu.Game.Rulesets.Catch.Tests CircleSize = 0, }; - var trailContainer = new Container(); - droppedObjectContainer = new DroppedObjectContainer(); - catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty); + if (catcher != null) + Remove(catcher); - Child = new Container + Add(catcher = new TestCatcher(trailContainer, difficulty) { - Anchor = Anchor.Centre, - Children = new Drawable[] - { - trailContainer, - droppedObjectContainer, - catcher - } - }; + Anchor = Anchor.Centre + }); }); [Test] @@ -293,8 +299,8 @@ namespace osu.Game.Rulesets.Catch.Tests { public IEnumerable CaughtObjects => this.ChildrenOfType(); - public TestCatcher(Container trailsTarget, DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty) - : base(trailsTarget, droppedObjectTarget, difficulty) + public TestCatcher(Container trailsTarget, BeatmapDifficulty difficulty) + : base(trailsTarget, difficulty) { } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index dca75eee14..877e115e2f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -96,15 +96,12 @@ namespace osu.Game.Rulesets.Catch.Tests SetContents(_ => { - var droppedObjectContainer = new DroppedObjectContainer(); - return new CatchInputManager(catchRuleset) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - droppedObjectContainer, - new TestCatcherArea(droppedObjectContainer, beatmapDifficulty) + new TestCatcherArea(beatmapDifficulty) { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, @@ -122,9 +119,13 @@ namespace osu.Game.Rulesets.Catch.Tests private class TestCatcherArea : CatcherArea { - public TestCatcherArea(DroppedObjectContainer droppedObjectContainer, BeatmapDifficulty beatmapDifficulty) - : base(droppedObjectContainer, beatmapDifficulty) + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + public TestCatcherArea(BeatmapDifficulty beatmapDifficulty) + : base(beatmapDifficulty) { + AddInternal(droppedObjectContainer = new DroppedObjectContainer()); } public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1); diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 4af528ef22..7fa981d492 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -118,11 +118,10 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("create hyper-dashing catcher", () => { - Child = setupSkinHierarchy(catcherArea = new CatcherArea(new DroppedObjectContainer()) + Child = setupSkinHierarchy(catcherArea = new TestCatcherArea { Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(4f), + Origin = Anchor.Centre }, skin); }); @@ -206,5 +205,18 @@ namespace osu.Game.Rulesets.Catch.Tests { } } + + private class TestCatcherArea : CatcherArea + { + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + public TestCatcherArea() + { + Scale = new Vector2(4f); + + AddInternal(droppedObjectContainer = new DroppedObjectContainer()); + } + } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 21501398fc..05cd29dff5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -26,6 +26,9 @@ namespace osu.Game.Rulesets.Catch.UI /// public const float CENTER_X = WIDTH / 2; + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + internal readonly CatcherArea CatcherArea; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => @@ -34,9 +37,7 @@ namespace osu.Game.Rulesets.Catch.UI public CatchPlayfield(BeatmapDifficulty difficulty) { - var droppedObjectContainer = new DroppedObjectContainer(); - - CatcherArea = new CatcherArea(droppedObjectContainer, difficulty) + CatcherArea = new CatcherArea(difficulty) { Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, @@ -44,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.UI InternalChildren = new[] { - droppedObjectContainer, + droppedObjectContainer = new DroppedObjectContainer(), CatcherArea.MovableCatcher.CreateProxiedContent(), HitObjectContainer.CreateProxy(), // This ordering (`CatcherArea` before `HitObjectContainer`) is important to diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 6b42e3ddd3..dcab9459ee 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -79,7 +79,8 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Contains objects dropped from the plate. /// - private readonly DroppedObjectContainer droppedObjectTarget; + [Resolved] + private DroppedObjectContainer droppedObjectTarget { get; set; } public CatcherAnimationState CurrentState { @@ -134,10 +135,9 @@ namespace osu.Game.Rulesets.Catch.UI private readonly DrawablePool caughtBananaPool; private readonly DrawablePool caughtDropletPool; - public Catcher([NotNull] Container trailsTarget, [NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null) + public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null) { this.trailsTarget = trailsTarget; - this.droppedObjectTarget = droppedObjectTarget; Origin = Anchor.TopCentre; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 951a10fc01..fea314df8d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.UI /// private int currentDirection; - public CatcherArea(DroppedObjectContainer droppedObjectContainer, BeatmapDifficulty difficulty = null) + public CatcherArea(BeatmapDifficulty difficulty = null) { Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); Children = new Drawable[] @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.UI Margin = new MarginPadding { Bottom = 350f }, X = CatchPlayfield.CENTER_X }, - MovableCatcher = new Catcher(this, droppedObjectContainer, difficulty) { X = CatchPlayfield.CENTER_X }, + MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X }, }; } From c0c1b8d62014bb0c23dad2e3b924051af5467303 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 24 Jun 2021 16:12:43 +0900 Subject: [PATCH 1031/1621] Fix catcher hyper-dash afterimage is not always displayed --- osu.Game.Rulesets.Catch/UI/CatcherTrail.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs index 80522ab36b..c961d98dc5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs @@ -37,6 +37,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override void FreeAfterUse() { ClearTransforms(); + Alpha = 1; base.FreeAfterUse(); } } From 4af119a407c649e364fe917fa63049f52b5cb4a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 16:29:06 +0900 Subject: [PATCH 1032/1621] Re-namespace --- osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs | 1 + .../Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs | 1 + osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs | 1 + osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs | 1 + .../Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs | 1 + .../Visual/Playlists/TestScenePlaylistsParticipantsList.cs | 1 + osu.Game/Tests/Visual/{ => OnlinePlay}/TestRoomContainer.cs | 2 +- 7 files changed, 7 insertions(+), 1 deletion(-) rename osu.Game/Tests/Visual/{ => OnlinePlay}/TestRoomContainer.cs (96%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs index a8cbd8b7ef..73f7865208 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index 2a254b1573..d114ae8abf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual.OnlinePlay; using osuTK; namespace osu.Game.Tests.Visual.Multiplayer diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 50e6426472..25cdfd4a90 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -7,6 +7,7 @@ using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 566dc9fc00..467df97cb9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -9,6 +9,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; using osuTK; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 3055a2c6c7..94aed2ecfa 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 083930698f..02ddfe4e79 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; namespace osu.Game.Tests.Visual.Playlists diff --git a/osu.Game/Tests/Visual/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs similarity index 96% rename from osu.Game/Tests/Visual/TestRoomContainer.cs rename to osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs index c4af89cd51..93dc836d23 100644 --- a/osu.Game/Tests/Visual/TestRoomContainer.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.Rooms; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.OnlinePlay { /// /// Contains a that is resolvable by components in test scenes. From 9287fae5f764610063e19976986d5b753e39f786 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 16:54:09 +0900 Subject: [PATCH 1033/1621] Simplify --- osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs index 93dc836d23..c2abcdfd08 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs @@ -19,19 +19,18 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// public readonly Room Room = new Room(); - [Cached] - private readonly Bindable roomBindable; - public TestRoomContainer() { RelativeSizeAxes = Axes.Both; - roomBindable = new Bindable(Room); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - var dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Model.Value = Room; + var dependencies = new DependencyContainer( + new CachedModelDependencyContainer(parent) { Model = { Value = Room } }); + + dependencies.Cache(new Bindable(Room)); + return dependencies; } } From a7b5c3bed124798ed1a9b46ff391bd4df63337cf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 17:01:28 +0900 Subject: [PATCH 1034/1621] Add OnlinePlaySubScreenTestScene --- .../TestScenePlaylistsSongSelect.cs | 3 +- .../TestScenePlaylistsRoomSubScreen.cs | 3 +- .../OnlinePlaySubScreenTestScene.cs | 64 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index d95a95ebe5..b958afd183 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -20,10 +20,11 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestScenePlaylistsSongSelect : RoomTestScene + public class TestScenePlaylistsSongSelect : OnlinePlaySubScreenTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index a08a91314b..73f9fcd8fd 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -19,12 +19,13 @@ using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; using osuTK.Input; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsRoomSubScreen : RoomTestScene + public class TestScenePlaylistsRoomSubScreen : OnlinePlaySubScreenTestScene { [Cached(typeof(IRoomManager))] private readonly TestRoomManager roomManager = new TestRoomManager(); diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs new file mode 100644 index 0000000000..27cb6496ce --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.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 System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Online.Rooms; +using osu.Game.Screens; +using osu.Game.Screens.OnlinePlay; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + public abstract class OnlinePlaySubScreenTestScene : ScreenTestScene + { + /// + /// The cached . + /// + protected Room Room { get; private set; } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("create dependencies", () => LoadScreen(new DependenciesScreen(CreateScreenDependencies))); + } + + /// + /// Creates dependencies for any pushed via . + /// Invoked at the start of every test via . + /// + /// + /// This should be overridden to add any custom dependencies required by subclasses of . + /// + /// The parent dependency container. + /// The resultant dependency container. + protected virtual IReadOnlyDependencyContainer CreateScreenDependencies(IReadOnlyDependencyContainer parent) + { + Room = new Room(); + + var dependencies = new DependencyContainer( + new CachedModelDependencyContainer(parent) { Model = { Value = Room } }); + + dependencies.Cache(new Bindable(Room)); + + return dependencies; + } + + /// + /// A dummy screen used for injecting new dependencies into the hierarchy before any screen is pushed via . + /// + private class DependenciesScreen : OsuScreen + { + private readonly Func createDependenciesFunc; + + public DependenciesScreen(Func createDependenciesFunc) + { + this.createDependenciesFunc = createDependenciesFunc; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + => createDependenciesFunc(base.CreateChildDependencies(parent)); + } + } +} From 6922de12c692c7ae6835579d868b1a92100487f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Jun 2021 17:17:12 +0900 Subject: [PATCH 1035/1621] Add extra null safety in dispose call --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7cc34114bf..d0be03f8c1 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -499,7 +499,7 @@ namespace osu.Game contextFactory.FlushConnections(); - if (Host != null) + if (Host?.UpdateThread != null) Host.UpdateThread.ThreadPausing -= onUpdateThreadPausing; } } From 5115299e9aa3fc7119dfa12784c1bee4a034ddb9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 19:09:31 +0900 Subject: [PATCH 1036/1621] Merge RoomManagerTestScene into OnlinePlaySubScreenTestScene --- .../Multiplayer/RoomManagerTestScene.cs | 61 ------------ .../Visual/Multiplayer/TestRoomManager.cs | 35 ------- .../TestSceneLoungeRoomsContainer.cs | 17 ++-- .../TestScenePlaylistsSongSelect.cs | 24 ++--- .../TestScenePlaylistsLoungeSubScreen.cs | 6 +- .../TestScenePlaylistsRoomSubScreen.cs | 58 +++-------- .../OnlinePlaySubScreenTestScene.cs | 95 +++++++++++++++++-- .../Visual/OnlinePlay/TestRoomContainer.cs | 2 +- 8 files changed, 129 insertions(+), 169 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs delete mode 100644 osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs deleted file mode 100644 index c665a57452..0000000000 --- a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Online.Rooms; -using osu.Game.Rulesets; -using osu.Game.Screens.OnlinePlay; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public abstract class RoomManagerTestScene : RoomTestScene - { - [Cached(Type = typeof(IRoomManager))] - protected TestRoomManager RoomManager { get; } = new TestRoomManager(); - - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("clear rooms", () => RoomManager.Rooms.Clear()); - } - - protected void AddRooms(int count, RulesetInfo ruleset = null) - { - AddStep("add rooms", () => - { - for (int i = 0; i < count; i++) - { - var room = new Room - { - RoomID = { Value = i }, - Name = { Value = $"Room {i}" }, - Host = { Value = new User { Username = "Host" } }, - EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, - Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } - }; - - if (ruleset != null) - { - room.Playlist.Add(new PlaylistItem - { - Ruleset = { Value = ruleset }, - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata() - } - } - }); - } - - RoomManager.Rooms.Add(room); - } - }); - } - } -} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs deleted file mode 100644 index 1785c99784..0000000000 --- a/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs +++ /dev/null @@ -1,35 +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.Bindables; -using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public class TestRoomManager : IRoomManager - { - public event Action RoomsUpdated - { - add { } - remove { } - } - - public readonly BindableList Rooms = new BindableList(); - - public IBindable InitialRoomsReceived { get; } = new Bindable(true); - - IBindableList IRoomManager.Rooms => Rooms; - - public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) => Rooms.Add(room); - - public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) - { - } - - public void PartRoom() - { - } - } -} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 5682fd5c3c..d63d52a463 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -10,12 +10,13 @@ using osu.Game.Online.Rooms; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Tests.Visual.OnlinePlay; using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneLoungeRoomsContainer : RoomManagerTestScene + public class TestSceneLoungeRoomsContainer : OnlinePlaySubScreenTestScene { private RoomsContainer container; @@ -34,7 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBasicListChanges() { - AddRooms(3); + AddStep("add rooms", () => RoomManager.AddRooms(3)); AddAssert("has 3 rooms", () => container.Rooms.Count == 3); AddStep("remove first room", () => RoomManager.Rooms.Remove(RoomManager.Rooms.FirstOrDefault())); @@ -51,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestKeyboardNavigation() { - AddRooms(3); + AddStep("add rooms", () => RoomManager.AddRooms(3)); AddAssert("no selection", () => checkRoomSelected(null)); @@ -72,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestClickDeselection() { - AddRooms(1); + AddStep("add room", () => RoomManager.AddRooms(1)); AddAssert("no selection", () => checkRoomSelected(null)); @@ -91,7 +92,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestStringFiltering() { - AddRooms(4); + AddStep("add rooms", () => RoomManager.AddRooms(4)); AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4); @@ -107,8 +108,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestRulesetFiltering() { - AddRooms(2, new OsuRuleset().RulesetInfo); - AddRooms(3, new CatchRuleset().RulesetInfo); + AddStep("add rooms", () => RoomManager.AddRooms(2, new OsuRuleset().RulesetInfo)); + AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo)); AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5); @@ -121,7 +122,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3); } - private bool checkRoomSelected(Room room) => Room == room; + private bool checkRoomSelected(Room room) => SelectedRoom.Value == room; private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index b958afd183..4f10877f95 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -14,6 +14,7 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -86,6 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("reset", () => { + SelectedRoom.Value = new Room(); Ruleset.Value = new OsuRuleset().RulesetInfo; Beatmap.SetDefault(); SelectedMods.Value = Array.Empty(); @@ -99,14 +101,14 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestItemAddedIfEmptyOnStart() { AddStep("finalise selection", () => songSelect.FinaliseSelection()); - AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); } [Test] public void TestItemAddedWhenCreateNewItemClicked() { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); } [Test] @@ -114,7 +116,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("finalise selection", () => songSelect.FinaliseSelection()); - AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); } [Test] @@ -122,7 +124,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddAssert("playlist has 2 items", () => Room.Playlist.Count == 2); + AddAssert("playlist has 2 items", () => SelectedRoom.Value.Playlist.Count == 2); } [Test] @@ -132,13 +134,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("rearrange", () => { - var item = Room.Playlist[0]; - Room.Playlist.RemoveAt(0); - Room.Playlist.Add(item); + var item = SelectedRoom.Value.Playlist[0]; + SelectedRoom.Value.Playlist.RemoveAt(0); + SelectedRoom.Value.Playlist.Add(item); }); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddAssert("new item has id 2", () => Room.Playlist.Last().ID == 2); + AddAssert("new item has id 2", () => SelectedRoom.Value.Playlist.Last().ID == 2); } /// @@ -152,8 +154,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change mod rate", () => ((OsuModDoubleTime)SelectedMods.Value[0]).SpeedChange.Value = 2); AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddAssert("item 1 has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value)); - AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)Room.Playlist.Last().RequiredMods[0]).SpeedChange.Value)); + AddAssert("item 1 has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0]).SpeedChange.Value)); + AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.Last().RequiredMods[0]).SpeedChange.Value)); } /// @@ -175,7 +177,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2); - AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value)); + AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0]).SpeedChange.Value)); } private class TestPlaylistsSongSelect : PlaylistsSongSelect diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 618447eae2..62a6f40d65 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -10,11 +10,11 @@ using osu.Game.Graphics.Containers; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Playlists; -using osu.Game.Tests.Visual.Multiplayer; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsLoungeSubScreen : RoomManagerTestScene + public class TestScenePlaylistsLoungeSubScreen : OnlinePlaySubScreenTestScene { private LoungeSubScreen loungeScreen; @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestScrollSelectedIntoView() { - AddRooms(30); + AddStep("add rooms", () => RoomManager.AddRooms(30)); AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms.First())); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 73f9fcd8fd..98efdd98ef 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -15,7 +15,6 @@ using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; @@ -27,9 +26,6 @@ namespace osu.Game.Tests.Visual.Playlists { public class TestScenePlaylistsRoomSubScreen : OnlinePlaySubScreenTestScene { - [Cached(typeof(IRoomManager))] - private readonly TestRoomManager roomManager = new TestRoomManager(); - private BeatmapManager manager; private RulesetStore rulesets; @@ -59,7 +55,8 @@ namespace osu.Game.Tests.Visual.Playlists [SetUpSteps] public void SetupSteps() { - AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(Room))); + AddStep("set room", () => SelectedRoom.Value = new Room()); + AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value))); AddUntilStep("wait for load", () => match.IsCurrentScreen()); } @@ -68,12 +65,12 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("set room properties", () => { - Room.RoomID.Value = 1; - Room.Name.Value = "my awesome room"; - Room.Host.Value = new User { Id = 2, Username = "peppy" }; - Room.RecentParticipants.Add(Room.Host.Value); - Room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.RoomID.Value = 1; + SelectedRoom.Value.Name.Value = "my awesome room"; + SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; + SelectedRoom.Value.RecentParticipants.Add(SelectedRoom.Value.Host.Value); + SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo } @@ -89,9 +86,9 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("set room properties", () => { - Room.Name.Value = "my awesome room"; - Room.Host.Value = new User { Id = 2, Username = "peppy" }; - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Name.Value = "my awesome room"; + SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo } @@ -105,7 +102,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddAssert("first playlist item selected", () => match.SelectedItem.Value == Room.Playlist[0]); + AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); } [Test] @@ -123,9 +120,9 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("load room", () => { - Room.Name.Value = "my awesome room"; - Room.Host.Value = new User { Id = 2, Username = "peppy" }; - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Name.Value = "my awesome room"; + SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = importedSet.Beatmaps[0] }, Ruleset = { Value = new OsuRuleset().RulesetInfo } @@ -156,30 +153,5 @@ namespace osu.Game.Tests.Visual.Playlists { } } - - private class TestRoomManager : IRoomManager - { - public event Action RoomsUpdated - { - add => throw new NotImplementedException(); - remove => throw new NotImplementedException(); - } - - public IBindable InitialRoomsReceived { get; } = new Bindable(true); - - public IBindableList Rooms { get; } = new BindableList(); - - public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) - { - room.RoomID.Value = 1; - onSuccess?.Invoke(room); - } - - public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); - - public void PartRoom() - { - } - } } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs index 27cb6496ce..177f6635a7 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs @@ -2,20 +2,37 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Beatmaps; using osu.Game.Online.Rooms; +using osu.Game.Rulesets; using osu.Game.Screens; using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { + /// + /// A providing all the dependencies cached by for testing s. + /// public abstract class OnlinePlaySubScreenTestScene : ScreenTestScene { /// - /// The cached . + /// The cached . /// - protected Room Room { get; private set; } + protected Bindable SelectedRoom { get; private set; } + + /// + /// The cached + /// + protected TestRoomManager RoomManager { get; private set; } + + protected Bindable Filter { get; private set; } + + protected OngoingOperationTracker OngoingOperationTracker { get; private set; } public override void SetUpSteps() { @@ -35,16 +52,80 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// The resultant dependency container. protected virtual IReadOnlyDependencyContainer CreateScreenDependencies(IReadOnlyDependencyContainer parent) { - Room = new Room(); + SelectedRoom = new Bindable(); + RoomManager = new TestRoomManager(); + Filter = new Bindable(new FilterCriteria()); + OngoingOperationTracker = new OngoingOperationTracker(); - var dependencies = new DependencyContainer( - new CachedModelDependencyContainer(parent) { Model = { Value = Room } }); - - dependencies.Cache(new Bindable(Room)); + var dependencies = new DependencyContainer(new CachedModelDependencyContainer(parent) { Model = { BindTarget = SelectedRoom } }); + dependencies.CacheAs(SelectedRoom); + dependencies.CacheAs(RoomManager); + dependencies.CacheAs(Filter); + dependencies.CacheAs(OngoingOperationTracker); return dependencies; } + protected class TestRoomManager : IRoomManager + { + public event Action RoomsUpdated + { + add { } + remove { } + } + + public readonly BindableList Rooms = new BindableList(); + + public IBindable InitialRoomsReceived { get; } = new Bindable(true); + + IBindableList IRoomManager.Rooms => Rooms; + + public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + { + room.RoomID.Value ??= Rooms.Select(r => r.RoomID.Value).Where(id => id != null).Select(id => id.Value).DefaultIfEmpty().Max() + 1; + Rooms.Add(room); + onSuccess?.Invoke(room); + } + + public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); + + public void PartRoom() + { + } + + public void AddRooms(int count, RulesetInfo ruleset = null) + { + for (int i = 0; i < count; i++) + { + var room = new Room + { + RoomID = { Value = i }, + Name = { Value = $"Room {i}" }, + Host = { Value = new User { Username = "Host" } }, + EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, + Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } + }; + + if (ruleset != null) + { + room.Playlist.Add(new PlaylistItem + { + Ruleset = { Value = ruleset }, + Beatmap = + { + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata() + } + } + }); + } + + CreateRoom(room); + } + } + } + /// /// A dummy screen used for injecting new dependencies into the hierarchy before any screen is pushed via . /// diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs index c2abcdfd08..7ff2e5133e 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs @@ -10,7 +10,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// Contains a that is resolvable by components in test scenes. + /// Contains a single that is resolvable by components in test scenes. /// public class TestRoomContainer : Container { From a21cf87b5fcee946600bbf22100397094383fbfe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 19:13:50 +0900 Subject: [PATCH 1037/1621] Split room manager and allow overrides --- .../TestSceneLoungeRoomsContainer.cs | 5 ++ .../TestScenePlaylistsLoungeSubScreen.cs | 9 +-- .../OnlinePlaySubScreenTestScene.cs | 70 +----------------- .../Visual/OnlinePlay/TestBasicRoomManager.cs | 74 +++++++++++++++++++ 4 files changed, 87 insertions(+), 71 deletions(-) create mode 100644 osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index d63d52a463..22e7acce44 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Visual.OnlinePlay; using osuTK.Graphics; @@ -18,6 +19,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneLoungeRoomsContainer : OnlinePlaySubScreenTestScene { + protected new TestBasicRoomManager RoomManager => (TestBasicRoomManager)base.RoomManager; + private RoomsContainer container; [BackgroundDependencyLoader] @@ -32,6 +35,8 @@ namespace osu.Game.Tests.Visual.Multiplayer }; } + protected override IRoomManager CreateRoomManager() => new TestBasicRoomManager(); + [Test] public void TestBasicListChanges() { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 62a6f40d65..34e66b9cb2 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -3,10 +3,10 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics.Containers; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Playlists; @@ -16,12 +16,11 @@ namespace osu.Game.Tests.Visual.Playlists { public class TestScenePlaylistsLoungeSubScreen : OnlinePlaySubScreenTestScene { + protected new TestBasicRoomManager RoomManager => (TestBasicRoomManager)base.RoomManager; + private LoungeSubScreen loungeScreen; - [BackgroundDependencyLoader] - private void load() - { - } + protected override IRoomManager CreateRoomManager() => new TestBasicRoomManager(); public override void SetUpSteps() { diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs index 177f6635a7..c4a8658d93 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs @@ -2,16 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Beatmaps; using osu.Game.Online.Rooms; -using osu.Game.Rulesets; using osu.Game.Screens; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; -using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { @@ -28,7 +24,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// The cached /// - protected TestRoomManager RoomManager { get; private set; } + protected IRoomManager RoomManager { get; private set; } protected Bindable Filter { get; private set; } @@ -53,78 +49,20 @@ namespace osu.Game.Tests.Visual.OnlinePlay protected virtual IReadOnlyDependencyContainer CreateScreenDependencies(IReadOnlyDependencyContainer parent) { SelectedRoom = new Bindable(); - RoomManager = new TestRoomManager(); + RoomManager = CreateRoomManager(); Filter = new Bindable(new FilterCriteria()); OngoingOperationTracker = new OngoingOperationTracker(); var dependencies = new DependencyContainer(new CachedModelDependencyContainer(parent) { Model = { BindTarget = SelectedRoom } }); dependencies.CacheAs(SelectedRoom); - dependencies.CacheAs(RoomManager); + dependencies.CacheAs(RoomManager); dependencies.CacheAs(Filter); dependencies.CacheAs(OngoingOperationTracker); return dependencies; } - protected class TestRoomManager : IRoomManager - { - public event Action RoomsUpdated - { - add { } - remove { } - } - - public readonly BindableList Rooms = new BindableList(); - - public IBindable InitialRoomsReceived { get; } = new Bindable(true); - - IBindableList IRoomManager.Rooms => Rooms; - - public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) - { - room.RoomID.Value ??= Rooms.Select(r => r.RoomID.Value).Where(id => id != null).Select(id => id.Value).DefaultIfEmpty().Max() + 1; - Rooms.Add(room); - onSuccess?.Invoke(room); - } - - public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); - - public void PartRoom() - { - } - - public void AddRooms(int count, RulesetInfo ruleset = null) - { - for (int i = 0; i < count; i++) - { - var room = new Room - { - RoomID = { Value = i }, - Name = { Value = $"Room {i}" }, - Host = { Value = new User { Username = "Host" } }, - EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, - Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } - }; - - if (ruleset != null) - { - room.Playlist.Add(new PlaylistItem - { - Ruleset = { Value = ruleset }, - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata() - } - } - }); - } - - CreateRoom(room); - } - } - } + protected virtual IRoomManager CreateRoomManager() => new TestBasicRoomManager(); /// /// A dummy screen used for injecting new dependencies into the hierarchy before any screen is pushed via . diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs new file mode 100644 index 0000000000..fd81072651 --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.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 System; +using System.Linq; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + public class TestBasicRoomManager : IRoomManager + { + public event Action RoomsUpdated + { + add { } + remove { } + } + + public readonly BindableList Rooms = new BindableList(); + + public IBindable InitialRoomsReceived { get; } = new Bindable(true); + + IBindableList IRoomManager.Rooms => Rooms; + + public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + { + room.RoomID.Value ??= Rooms.Select(r => r.RoomID.Value).Where(id => id != null).Select(id => id.Value).DefaultIfEmpty().Max() + 1; + Rooms.Add(room); + onSuccess?.Invoke(room); + } + + public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); + + public void PartRoom() + { + } + + public void AddRooms(int count, RulesetInfo ruleset = null) + { + for (int i = 0; i < count; i++) + { + var room = new Room + { + RoomID = { Value = i }, + Name = { Value = $"Room {i}" }, + Host = { Value = new User { Username = "Host" } }, + EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, + Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } + }; + + if (ruleset != null) + { + room.Playlist.Add(new PlaylistItem + { + Ruleset = { Value = ruleset }, + Beatmap = + { + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata() + } + } + }); + } + + CreateRoom(room); + } + } + } +} From 9cf2c9ddaef526ec9cc3ed8f3b08e8247e1b1765 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 21:02:21 +0900 Subject: [PATCH 1038/1621] Fix incorrect dependency creation --- osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs index 7ff2e5133e..d75281e772 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer( - new CachedModelDependencyContainer(parent) { Model = { Value = Room } }); + new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) { Model = { Value = Room } }); dependencies.Cache(new Bindable(Room)); From 5820793d1119f82bfeb772a835cfdd220277a356 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 20:44:48 +0900 Subject: [PATCH 1039/1621] Make TestMultiplayerRoomContainer inherit TestRoomContainer --- .../Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs index 1abf4d8f5d..d44b55c3c1 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs @@ -8,10 +8,11 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestMultiplayerRoomContainer : Container + public class TestMultiplayerRoomContainer : TestRoomContainer { protected override Container Content => content; private readonly Container content; From 8fba7d2423774f35c5e1182c4dcfe22992f9dad9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Jun 2021 21:02:04 +0900 Subject: [PATCH 1040/1621] Remove MultiplayerTestScene inheritance from simple test scenes --- .../TestSceneFreeModSelectOverlay.cs | 11 +- .../TestSceneMultiSpectatorLeaderboard.cs | 112 +++++++++--------- 2 files changed, 66 insertions(+), 57 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 26a0301d8a..25de2d0df3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -7,14 +7,17 @@ using osu.Game.Screens.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneFreeModSelectOverlay : MultiplayerTestScene + public class TestSceneFreeModSelectOverlay : OsuTestScene { [SetUp] - public new void Setup() => Schedule(() => + public void Setup() => Schedule(() => { - Child = new FreeModSelectOverlay + Child = new TestMultiplayerRoomContainer { - State = { Value = Visibility.Visible } + Child = new FreeModSelectOverlay + { + State = { Value = Visibility.Visible } + } }; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 5ad35be0ec..9ec85d360f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -1,15 +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 System.Linq; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Database; using osu.Game.Online.Spectator; @@ -21,61 +24,38 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene + public class TestSceneMultiSpectatorLeaderboard : OsuTestScene { - [Cached(typeof(SpectatorClient))] - private TestSpectatorClient spectatorClient = new TestSpectatorClient(); + private Dictionary clocks; - [Cached(typeof(UserLookupCache))] - private UserLookupCache lookupCache = new TestUserLookupCache(); - - protected override Container Content => content; - private readonly Container content; - - private readonly Dictionary clocks = new Dictionary - { - { PLAYER_1_ID, new ManualClock() }, - { PLAYER_2_ID, new ManualClock() } - }; - - public TestSceneMultiSpectatorLeaderboard() - { - base.Content.AddRange(new Drawable[] - { - spectatorClient, - lookupCache, - content = new Container { RelativeSizeAxes = Axes.Both } - }); - } + private MultiSpectatorLeaderboard leaderboard; + private TestContainer container; [SetUpSteps] - public new void SetUpSteps() + public void SetUpSteps() { - MultiSpectatorLeaderboard leaderboard = null; - AddStep("reset", () => { - Clear(); - - foreach (var (userId, clock) in clocks) + clocks = new Dictionary { - spectatorClient.EndPlay(userId); - clock.CurrentTime = 0; - } + { MultiplayerTestScene.PLAYER_1_ID, new ManualClock() }, + { MultiplayerTestScene.PLAYER_2_ID, new ManualClock() } + }; + + Child = container = new TestContainer(); + + foreach (var (userId, _) in clocks) + container.SpectatorClient.StartPlay(userId, 0); }); AddStep("create leaderboard", () => { - foreach (var (userId, _) in clocks) - spectatorClient.StartPlay(userId, 0); - Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); - var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); var scoreProcessor = new OsuScoreProcessor(); scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add); + container.LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, container.Add); }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); @@ -96,42 +76,42 @@ namespace osu.Game.Tests.Visual.Multiplayer // For player 2, send frames in sets of 10. for (int i = 0; i < 100; i++) { - spectatorClient.SendFrames(PLAYER_1_ID, i, 1); + container.SpectatorClient.SendFrames(MultiplayerTestScene.PLAYER_1_ID, i, 1); if (i % 10 == 0) - spectatorClient.SendFrames(PLAYER_2_ID, i, 10); + container.SpectatorClient.SendFrames(MultiplayerTestScene.PLAYER_2_ID, i, 10); } }); - assertCombo(PLAYER_1_ID, 1); - assertCombo(PLAYER_2_ID, 10); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 1); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 10); // Advance to a point where only user player 1's frame changes. setTime(500); - assertCombo(PLAYER_1_ID, 5); - assertCombo(PLAYER_2_ID, 10); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 5); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 10); // Advance to a point where both user's frame changes. setTime(1100); - assertCombo(PLAYER_1_ID, 11); - assertCombo(PLAYER_2_ID, 20); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 11); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 20); // Advance user player 2 only to a point where its frame changes. - setTime(PLAYER_2_ID, 2100); - assertCombo(PLAYER_1_ID, 11); - assertCombo(PLAYER_2_ID, 30); + setTime(MultiplayerTestScene.PLAYER_2_ID, 2100); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 11); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 30); // Advance both users beyond their last frame setTime(101 * 100); - assertCombo(PLAYER_1_ID, 100); - assertCombo(PLAYER_2_ID, 100); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 100); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 100); } [Test] public void TestNoFrames() { - assertCombo(PLAYER_1_ID, 0); - assertCombo(PLAYER_2_ID, 0); + assertCombo(MultiplayerTestScene.PLAYER_1_ID, 0); + assertCombo(MultiplayerTestScene.PLAYER_2_ID, 0); } private void setTime(double time) => AddStep($"set time {time}", () => @@ -146,6 +126,32 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertCombo(int userId, int expectedCombo) => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo); + private class TestContainer : TestMultiplayerRoomContainer + { + [Cached(typeof(SpectatorClient))] + public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); + + [Cached(typeof(UserLookupCache))] + public readonly UserLookupCache LookupCache = new TestUserLookupCache(); + + protected override Container Content => content; + private readonly Container content; + + public TestContainer() + { + AddRangeInternal(new Drawable[] + { + SpectatorClient, + LookupCache, + content = new Container { RelativeSizeAxes = Axes.Both } + }); + } + + public new Task LoadComponentAsync([NotNull] TLoadable component, Action onLoaded = null, CancellationToken cancellation = default, Scheduler scheduler = null) + where TLoadable : Drawable + => base.LoadComponentAsync(component, onLoaded, cancellation, scheduler); + } + private class TestUserLookupCache : UserLookupCache { protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) From 3fcda83713111f24fc1436adfdd588d2aa6cea3a Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 22:00:19 +0800 Subject: [PATCH 1041/1621] Rename `VectorHelper` to `VectorUtils` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- osu.Game.Rulesets.Osu/Utils/{VectorHelper.cs => VectorUtils.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/Utils/{VectorHelper.cs => VectorUtils.cs} (99%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index bcd5274e02..7c7e88cbba 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Mods distanceToPrev * (float)Math.Sin(current.AngleRad) ); - posRelativeToPrev = VectorHelper.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); + posRelativeToPrev = VectorUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); diff --git a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs b/osu.Game.Rulesets.Osu/Utils/VectorUtils.cs similarity index 99% rename from osu.Game.Rulesets.Osu/Utils/VectorHelper.cs rename to osu.Game.Rulesets.Osu/Utils/VectorUtils.cs index e3fe3249e8..a829c89636 100644 --- a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs +++ b/osu.Game.Rulesets.Osu/Utils/VectorUtils.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Utils { - public static class VectorHelper + public static class VectorUtils { // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn From 4fe30ca9235d1fa1937879b2896cdddb7a3c21c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 24 Jun 2021 18:31:13 +0200 Subject: [PATCH 1042/1621] Remove Traditional Chinese (HK) language option Due to no actual existing translations it was falling back to Simplified Chinese instead, making the option actively wrong/confusing. --- osu.Game/Localisation/Language.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index 3c66f31c58..dc1fac47a8 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -103,8 +103,11 @@ namespace osu.Game.Localisation [Description(@"简体中文")] zh, - [Description(@"繁體中文(香港)")] - zh_hk, + // Traditional Chinese (Hong Kong) is listed in web sources but has no associated localisations, + // and was wrongly falling back to Simplified Chinese. + // Can be revisited if localisations ever arrive. + // [Description(@"繁體中文(香港)")] + // zh_hk, [Description(@"繁體中文(台灣)")] zh_tw From 62566f2a4a0caf84a211e6cf7469f04abfa5900f Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Thu, 24 Jun 2021 14:29:47 -0700 Subject: [PATCH 1043/1621] Remove "Score Multiplier" text --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e31e307d4d..85dcade986 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -330,22 +330,12 @@ namespace osu.Game.Overlays.Mods Spacing = new Vector2(footer_button_spacing / 2, 0), Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Children = new Drawable[] + Child = MultiplierLabel = new OsuSpriteText { - new OsuSpriteText - { - Text = @"Score Multiplier:", - Font = OsuFont.GetFont(size: 30), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - MultiplierLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Width = 70, // make width fixed so reflow doesn't occur when multiplier number changes. - }, + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 70, // make width fixed so reflow doesn't occur when multiplier number changes. }, }, } From 34ace2553e5b8db36be5dd0447bac569eeb2d843 Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Thu, 24 Jun 2021 14:32:00 -0700 Subject: [PATCH 1044/1621] Revert "Refactor the menu's max height to be a property" This reverts commit 9cb9ef5c --- osu.Game/Overlays/Settings/SettingsEnumDropdown.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index fa9973fb08..9987a0c607 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -14,15 +14,13 @@ namespace osu.Game.Overlays.Settings protected new class DropdownControl : OsuEnumDropdown { - protected virtual int MenuMaxHeight => 200; - public DropdownControl() { Margin = new MarginPadding { Top = 5 }; RelativeSizeAxes = Axes.X; } - protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = MenuMaxHeight); + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200); } } } From 93edb25acefe5004a7fd4e032d8a7696ca02bbdd Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Thu, 24 Jun 2021 14:32:43 -0700 Subject: [PATCH 1045/1621] Override `CreateMenu` instead of using a property --- osu.Game/Configuration/SettingSourceAttribute.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index cee5883d9a..55636495df 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -192,8 +192,8 @@ namespace osu.Game.Configuration private class ModDropdownControl : DropdownControl { - // Set low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). - protected override int MenuMaxHeight => 100; + // Set menu's max height low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 100); } } } From 26086ca1efc5107ab3c125a24a146db17940e1bb Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 25 Jun 2021 09:43:14 +0800 Subject: [PATCH 1046/1621] Rename `VectorUtils` to `OsuHitObjectGenerationUtils` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- .../Utils/{VectorUtils.cs => OsuHitObjectGenerationUtils.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/Utils/{VectorUtils.cs => OsuHitObjectGenerationUtils.cs} (98%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 7c7e88cbba..d1212096bf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Mods distanceToPrev * (float)Math.Sin(current.AngleRad) ); - posRelativeToPrev = VectorUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); + posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); diff --git a/osu.Game.Rulesets.Osu/Utils/VectorUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs similarity index 98% rename from osu.Game.Rulesets.Osu/Utils/VectorUtils.cs rename to osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index a829c89636..2a60124757 100644 --- a/osu.Game.Rulesets.Osu/Utils/VectorUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Utils { - public static class VectorUtils + public static class OsuHitObjectGenerationUtils { // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn From ec8810cc2b96e92108c5fd365366c0fe8e9d97be Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 25 Jun 2021 09:44:23 +0800 Subject: [PATCH 1047/1621] Use `MathF` instead of `(float)Math` --- .../Utils/OsuHitObjectGenerationUtils.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 2a60124757..06b964a647 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -84,20 +84,20 @@ namespace osu.Game.Rulesets.Osu.Utils /// The rotated vector. public static Vector2 RotateVectorTowardsVector(Vector2 initial, Vector2 destination, float rotationRatio) { - var initialAngleRad = Math.Atan2(initial.Y, initial.X); - var destAngleRad = Math.Atan2(destination.Y, destination.X); + var initialAngleRad = MathF.Atan2(initial.Y, initial.X); + var destAngleRad = MathF.Atan2(destination.Y, destination.X); var diff = destAngleRad - initialAngleRad; - while (diff < -Math.PI) diff += 2 * Math.PI; + while (diff < -MathF.PI) diff += 2 * MathF.PI; - while (diff > Math.PI) diff -= 2 * Math.PI; + while (diff > MathF.PI) diff -= 2 * MathF.PI; var finalAngleRad = initialAngleRad + rotationRatio * diff; return new Vector2( - initial.Length * (float)Math.Cos(finalAngleRad), - initial.Length * (float)Math.Sin(finalAngleRad) + initial.Length * MathF.Cos(finalAngleRad), + initial.Length * MathF.Sin(finalAngleRad) ); } } From 31c786e1c306e1b0a5161e644b2d12009cc91143 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 25 Jun 2021 09:51:34 +0800 Subject: [PATCH 1048/1621] Use `SettingsNumberBox.NumberBox` instead of `OsuNumberBox` --- osu.Game/Rulesets/Mods/SeedSettingsControl.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs index 5c57717d93..1280197532 100644 --- a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs @@ -5,7 +5,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods @@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Mods } } - private readonly OsuNumberBox seedNumberBox; + private readonly SettingsNumberBox.NumberBox seedNumberBox; public SeedControl() { @@ -61,7 +60,7 @@ namespace osu.Game.Rulesets.Mods { new Drawable[] { - seedNumberBox = new OsuNumberBox + seedNumberBox = new SettingsNumberBox.NumberBox { RelativeSizeAxes = Axes.X, CommitOnFocusLost = true From aa5d22d04a4ecf0c0bc315ae7ce3ad1fd7b50b32 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 13:02:19 +0900 Subject: [PATCH 1049/1621] Remove "test container", make everything go through OnlinePlayTestScene --- .../Multiplayer/TestSceneLoungeRoomInfo.cs | 32 +++--- .../TestSceneLoungeRoomsContainer.cs | 7 +- .../TestSceneMatchBeatmapDetailArea.cs | 25 ++--- .../Multiplayer/TestSceneMatchHeader.cs | 19 ++-- .../Multiplayer/TestSceneMatchLeaderboard.cs | 20 ++-- .../TestSceneMultiplayerMatchFooter.cs | 16 +-- .../TestScenePlaylistsSongSelect.cs | 2 +- .../TestScenePlaylistsLoungeSubScreen.cs | 7 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 49 +++++---- .../TestScenePlaylistsParticipantsList.cs | 18 ++-- .../TestScenePlaylistsRoomSubScreen.cs | 2 +- .../IMultiplayerRoomTestDependencies.cs | 22 ++++ .../MultiplayerRoomTestDependencies.cs | 23 ++++ .../MultiplayerSubScreenTestScene.cs | 17 +++ .../TestMultiplayerRoomContainer.cs | 3 +- ...RoomManager.cs => BasicTestRoomManager.cs} | 2 +- .../OnlinePlay/IRoomTestDependencies.cs | 38 +++++++ .../OnlinePlaySubScreenTestScene.cs | 83 --------------- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 100 ++++++++++++++++++ .../Visual/OnlinePlay/RoomTestDependencies.cs | 78 ++++++++++++++ .../Visual/OnlinePlay/TestRoomContainer.cs | 37 ------- 21 files changed, 370 insertions(+), 230 deletions(-) create mode 100644 osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs create mode 100644 osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs create mode 100644 osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs rename osu.Game/Tests/Visual/OnlinePlay/{TestBasicRoomManager.cs => BasicTestRoomManager.cs} (97%) create mode 100644 osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs delete mode 100644 osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs create mode 100644 osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs create mode 100644 osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs delete mode 100644 osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs index 73f7865208..471d0b6c98 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs @@ -4,6 +4,7 @@ using System; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Visual.OnlinePlay; @@ -11,28 +12,25 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneLoungeRoomInfo : OsuTestScene + public class TestSceneLoungeRoomInfo : OnlinePlayTestScene { - private TestRoomContainer roomContainer; - [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer + SelectedRoom.Value = new Room(); + + Child = new RoomInfo { - Child = new RoomInfo - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 500 - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500 }; }); [Test] public void TestNonSelectedRoom() { - AddStep("set null room", () => roomContainer.Room.RoomID.Value = null); + AddStep("set null room", () => SelectedRoom.Value.RoomID.Value = null); } [Test] @@ -40,11 +38,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set open room", () => { - roomContainer.Room.RoomID.Value = 0; - roomContainer.Room.Name.Value = "Room 0"; - roomContainer.Room.Host.Value = new User { Username = "peppy", Id = 2 }; - roomContainer.Room.EndDate.Value = DateTimeOffset.Now.AddMonths(1); - roomContainer.Room.Status.Value = new RoomStatusOpen(); + SelectedRoom.Value.RoomID.Value = 0; + SelectedRoom.Value.Name.Value = "Room 0"; + SelectedRoom.Value.Host.Value = new User { Username = "peppy", Id = 2 }; + SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMonths(1); + SelectedRoom.Value.Status.Value = new RoomStatusOpen(); }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 22e7acce44..a78cff0650 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -9,7 +9,6 @@ using osu.Game.Graphics; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Visual.OnlinePlay; using osuTK.Graphics; @@ -17,9 +16,9 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneLoungeRoomsContainer : OnlinePlaySubScreenTestScene + public class TestSceneLoungeRoomsContainer : OnlinePlayTestScene { - protected new TestBasicRoomManager RoomManager => (TestBasicRoomManager)base.RoomManager; + protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager; private RoomsContainer container; @@ -35,8 +34,6 @@ namespace osu.Game.Tests.Visual.Multiplayer }; } - protected override IRoomManager CreateRoomManager() => new TestBasicRoomManager(); - [Test] public void TestBasicListChanges() { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index d114ae8abf..d66603a448 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchBeatmapDetailArea : OsuTestScene + public class TestSceneMatchBeatmapDetailArea : OnlinePlayTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -24,28 +24,25 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private RulesetStore rulesetStore { get; set; } - private TestRoomContainer roomContainer; - [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer + SelectedRoom.Value = new Room(); + + Child = new MatchBeatmapDetailArea { - Child = new MatchBeatmapDetailArea - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500), - CreateNewItem = createNewItem - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500), + CreateNewItem = createNewItem }; }); private void createNewItem() { - roomContainer.Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { - ID = roomContainer.Room.Playlist.Count, + ID = SelectedRoom.Value.Playlist.Count, Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, RequiredMods = diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 25cdfd4a90..3557bd9127 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -12,17 +12,14 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchHeader : OsuTestScene + public class TestSceneMatchHeader : OnlinePlayTestScene { - private TestRoomContainer roomContainer; - [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer - { - Child = new Header() - }; + SelectedRoom.Value = new Room(); + + Child = new Header(); }); [Test] @@ -30,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set basic room", () => { - roomContainer.Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { @@ -54,8 +51,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - roomContainer.Room.Name.Value = "A very awesome room"; - roomContainer.Room.Host.Value = new User { Id = 2, Username = "peppy" }; + SelectedRoom.Value.Name.Value = "A very awesome room"; + SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 467df97cb9..a7a5f3af39 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchLeaderboard : OsuTestScene + public class TestSceneMatchLeaderboard : OnlinePlayTestScene { [BackgroundDependencyLoader] private void load() @@ -59,18 +59,16 @@ namespace osu.Game.Tests.Visual.Multiplayer } [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = new TestRoomContainer + SelectedRoom.Value = new Room { RoomID = { Value = 3 } }; + + Child = new MatchLeaderboard { - Room = { RoomID = { Value = 3 } }, - Child = new MatchLeaderboard - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(550f, 450f), - Scope = MatchLeaderboardScope.Overall, - } + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(550f, 450f), + Scope = MatchLeaderboardScope.Overall, }; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index 6b03b53b4b..afe66d2686 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -1,27 +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.Allocation; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene + public class TestSceneMultiplayerMatchFooter : OnlinePlayTestScene { - [Cached] - private readonly OnlinePlayBeatmapAvailabilityTracker availablilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); - - [BackgroundDependencyLoader] - private void load() + [SetUp] + public new void Setup() => Schedule(() => { + SelectedRoom.Value = new Room(); + Child = new MultiplayerMatchFooter { Anchor = Anchor.Centre, Origin = Anchor.Centre, Height = 50 }; - } + }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 4f10877f95..e4bf9b36ed 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -25,7 +25,7 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestScenePlaylistsSongSelect : OnlinePlaySubScreenTestScene + public class TestScenePlaylistsSongSelect : OnlinePlayTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 34e66b9cb2..b16b61c5c7 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics.Containers; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Playlists; @@ -14,14 +13,12 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsLoungeSubScreen : OnlinePlaySubScreenTestScene + public class TestScenePlaylistsLoungeSubScreen : OnlinePlayTestScene { - protected new TestBasicRoomManager RoomManager => (TestBasicRoomManager)base.RoomManager; + protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager; private LoungeSubScreen loungeScreen; - protected override IRoomManager CreateRoomManager() => new TestBasicRoomManager(); - public override void SetUpSteps() { base.SetUpSteps(); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 94aed2ecfa..b52b6a6a0e 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -3,7 +3,6 @@ using System; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,24 +15,23 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsMatchSettingsOverlay : OsuTestScene + public class TestScenePlaylistsMatchSettingsOverlay : OnlinePlayTestScene { - [Cached(Type = typeof(IRoomManager))] - private TestRoomManager roomManager = new TestRoomManager(); + protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; - private TestRoomContainer roomContainer; private TestRoomSettings settings; + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer + SelectedRoom.Value = new Room(); + + Child = settings = new TestRoomSettings { - Child = settings = new TestRoomSettings - { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible } - } + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible } }; }); @@ -42,19 +40,19 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("clear name and beatmap", () => { - roomContainer.Room.Name.Value = ""; - roomContainer.Room.Playlist.Clear(); + SelectedRoom.Value.Name.Value = ""; + SelectedRoom.Value.Playlist.Clear(); }); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set name", () => roomContainer.Room.Name.Value = "Room name"); + AddStep("set name", () => SelectedRoom.Value.Name.Value = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set beatmap", () => roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); + AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); - AddStep("clear name", () => roomContainer.Room.Name.Value = ""); + AddStep("clear name", () => SelectedRoom.Value.Name.Value = ""); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); } @@ -70,9 +68,9 @@ namespace osu.Game.Tests.Visual.Playlists { settings.NameField.Current.Value = expected_name; settings.DurationField.Current.Value = expectedDuration; - roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); - roomManager.CreateRequested = r => + RoomManager.CreateRequested = r => { createdRoom = r; return true; @@ -91,11 +89,11 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("setup", () => { - roomContainer.Room.Name.Value = "Test Room"; - roomContainer.Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + SelectedRoom.Value.Name.Value = "Test Room"; + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); fail = true; - roomManager.CreateRequested = _ => !fail; + RoomManager.CreateRequested = _ => !fail; }); AddAssert("error not displayed", () => !settings.ErrorText.IsPresent); @@ -122,7 +120,12 @@ namespace osu.Game.Tests.Visual.Playlists public OsuSpriteText ErrorText => ((MatchSettings)Settings).ErrorText; } - private class TestRoomManager : IRoomManager + private class TestDependencies : RoomTestDependencies + { + protected override IRoomManager CreateRoomManager() => new TestRoomManager(); + } + + protected class TestRoomManager : IRoomManager { public const string FAILED_TEXT = "failed"; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 02ddfe4e79..76a78c0a3c 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -3,27 +3,23 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Users; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsParticipantsList : OsuTestScene + public class TestScenePlaylistsParticipantsList : OnlinePlayTestScene { - private TestRoomContainer roomContainer; - [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = roomContainer = new TestRoomContainer - { - Room = { RoomID = { Value = 7 } } - }; + SelectedRoom.Value = new Room { RoomID = { Value = 7 } }; for (int i = 0; i < 50; i++) { - roomContainer.Room.RecentParticipants.Add(new User + SelectedRoom.Value.RecentParticipants.Add(new User { Username = "peppy", Statistics = new UserStatistics { GlobalRank = 1234 }, @@ -37,7 +33,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - roomContainer.Child = new ParticipantsDisplay(Direction.Horizontal) + Child = new ParticipantsDisplay(Direction.Horizontal) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -51,7 +47,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - roomContainer.Child = new ParticipantsDisplay(Direction.Vertical) + Child = new ParticipantsDisplay(Direction.Vertical) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 98efdd98ef..a0096c823f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -24,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsRoomSubScreen : OnlinePlaySubScreenTestScene + public class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene { private BeatmapManager manager; private RulesetStore rulesets; diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs new file mode 100644 index 0000000000..0b53bdd04e --- /dev/null +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs @@ -0,0 +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 osu.Game.Online.Multiplayer; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public interface IMultiplayerRoomTestDependencies : IRoomTestDependencies + { + /// + /// The cached . + /// + TestMultiplayerClient Client { get; } + + /// + /// The cached . + /// + new TestMultiplayerRoomManager RoomManager { get; } + } +} diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs new file mode 100644 index 0000000000..e3913dd291 --- /dev/null +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerRoomTestDependencies + { + public TestMultiplayerClient Client { get; } + public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; + + public MultiplayerRoomTestDependencies() + { + Client = new TestMultiplayerClient(RoomManager); + CacheAs(Client); + } + + protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); + } +} diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs new file mode 100644 index 0000000000..5c405ee9d3 --- /dev/null +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs @@ -0,0 +1,17 @@ +// 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.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class MultiplayerSubScreenTestScene : OnlinePlayTestScene, IMultiplayerRoomTestDependencies + { + public TestMultiplayerClient Client => RoomDependencies.Client; + public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; + + protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; + + protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); + } +} diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs index d44b55c3c1..1abf4d8f5d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs @@ -8,11 +8,10 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; -using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestMultiplayerRoomContainer : TestRoomContainer + public class TestMultiplayerRoomContainer : Container { protected override Container Content => content; private readonly Container content; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs similarity index 97% rename from osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs rename to osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs index fd81072651..67beea9117 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestBasicRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs @@ -12,7 +12,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { - public class TestBasicRoomManager : IRoomManager + public class BasicTestRoomManager : IRoomManager { public event Action RoomsUpdated { diff --git a/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs new file mode 100644 index 0000000000..848e2aa77f --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs @@ -0,0 +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 osu.Framework.Bindables; +using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Lounge.Components; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + public interface IRoomTestDependencies + { + /// + /// The cached . + /// + Bindable SelectedRoom { get; } + + /// + /// The cached + /// + IRoomManager RoomManager { get; } + + /// + /// The cached . + /// + Bindable Filter { get; } + + /// + /// The cached . + /// + OngoingOperationTracker OngoingOperationTracker { get; } + + /// + /// The cached . + /// + OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } + } +} diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs deleted file mode 100644 index c4a8658d93..0000000000 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlaySubScreenTestScene.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Online.Rooms; -using osu.Game.Screens; -using osu.Game.Screens.OnlinePlay; -using osu.Game.Screens.OnlinePlay.Lounge.Components; - -namespace osu.Game.Tests.Visual.OnlinePlay -{ - /// - /// A providing all the dependencies cached by for testing s. - /// - public abstract class OnlinePlaySubScreenTestScene : ScreenTestScene - { - /// - /// The cached . - /// - protected Bindable SelectedRoom { get; private set; } - - /// - /// The cached - /// - protected IRoomManager RoomManager { get; private set; } - - protected Bindable Filter { get; private set; } - - protected OngoingOperationTracker OngoingOperationTracker { get; private set; } - - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("create dependencies", () => LoadScreen(new DependenciesScreen(CreateScreenDependencies))); - } - - /// - /// Creates dependencies for any pushed via . - /// Invoked at the start of every test via . - /// - /// - /// This should be overridden to add any custom dependencies required by subclasses of . - /// - /// The parent dependency container. - /// The resultant dependency container. - protected virtual IReadOnlyDependencyContainer CreateScreenDependencies(IReadOnlyDependencyContainer parent) - { - SelectedRoom = new Bindable(); - RoomManager = CreateRoomManager(); - Filter = new Bindable(new FilterCriteria()); - OngoingOperationTracker = new OngoingOperationTracker(); - - var dependencies = new DependencyContainer(new CachedModelDependencyContainer(parent) { Model = { BindTarget = SelectedRoom } }); - dependencies.CacheAs(SelectedRoom); - dependencies.CacheAs(RoomManager); - dependencies.CacheAs(Filter); - dependencies.CacheAs(OngoingOperationTracker); - - return dependencies; - } - - protected virtual IRoomManager CreateRoomManager() => new TestBasicRoomManager(); - - /// - /// A dummy screen used for injecting new dependencies into the hierarchy before any screen is pushed via . - /// - private class DependenciesScreen : OsuScreen - { - private readonly Func createDependenciesFunc; - - public DependenciesScreen(Func createDependenciesFunc) - { - this.createDependenciesFunc = createDependenciesFunc; - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - => createDependenciesFunc(base.CreateChildDependencies(parent)); - } - } -} diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs new file mode 100644 index 0000000000..5e46ea0544 --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -0,0 +1,100 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Lounge.Components; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + /// + /// A providing all the dependencies cached by for testing s. + /// + public abstract class OnlinePlayTestScene : ScreenTestScene, IRoomTestDependencies + { + public Bindable SelectedRoom => RoomDependencies?.SelectedRoom; + public IRoomManager RoomManager => RoomDependencies?.RoomManager; + public Bindable Filter => RoomDependencies?.Filter; + public OngoingOperationTracker OngoingOperationTracker => RoomDependencies?.OngoingOperationTracker; + public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => RoomDependencies?.AvailabilityTracker; + + protected RoomTestDependencies RoomDependencies => delegatedDependencies?.RoomDependencies; + private DelegatedRoomDependencyContainer delegatedDependencies; + + protected override Container Content => content; + private readonly Container content; + private readonly Container drawableDependenciesContainer; + + protected OnlinePlayTestScene() + { + base.Content.AddRange(new Drawable[] + { + drawableDependenciesContainer = new Container { RelativeSizeAxes = Axes.Both }, + content = new Container { RelativeSizeAxes = Axes.Both }, + }); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + delegatedDependencies = new DelegatedRoomDependencyContainer(base.CreateChildDependencies(parent)); + return delegatedDependencies; + } + + [SetUp] + public void Setup() => Schedule(() => + { + // Reset the room dependencies to a fresh state. + drawableDependenciesContainer.Clear(); + delegatedDependencies.RoomDependencies = CreateRoomDependencies(); + drawableDependenciesContainer.AddRange(RoomDependencies.DrawableComponents); + }); + + /// + /// Creates the room dependencies. Called every . + /// + /// + /// Any custom dependencies required for online-play sub-classes should be added here. + /// + protected virtual RoomTestDependencies CreateRoomDependencies() => new RoomTestDependencies(); + + /// + /// A providing a mutable lookup source for room dependencies. + /// + private class DelegatedRoomDependencyContainer : IReadOnlyDependencyContainer + { + /// + /// The room's dependencies. + /// + public RoomTestDependencies RoomDependencies { get; set; } + + private readonly IReadOnlyDependencyContainer parent; + private readonly DependencyContainer injectableDependencies; + + /// + /// Creates a new . + /// + /// The fallback to use when cannot satisfy a dependency. + public DelegatedRoomDependencyContainer(IReadOnlyDependencyContainer parent) + { + this.parent = parent; + injectableDependencies = new DependencyContainer(this); + } + + public object Get(Type type) + => RoomDependencies?.Get(type) ?? parent.Get(type); + + public object Get(Type type, CacheInfo info) + => RoomDependencies?.Get(type, info) ?? parent.Get(type, info); + + public void Inject(T instance) + where T : class + => injectableDependencies.Inject(instance); + } + } +} diff --git a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs new file mode 100644 index 0000000000..9d22f9e286 --- /dev/null +++ b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs @@ -0,0 +1,78 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Lounge.Components; + +namespace osu.Game.Tests.Visual.OnlinePlay +{ + /// + /// Contains dependencies for testing online-play rooms. + /// + public class RoomTestDependencies : IReadOnlyDependencyContainer, IRoomTestDependencies + { + public Bindable SelectedRoom { get; } + public IRoomManager RoomManager { get; } + public Bindable Filter { get; } + public OngoingOperationTracker OngoingOperationTracker { get; } + public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } + + /// + /// All cached dependencies which are also components. + /// + public IReadOnlyList DrawableComponents => drawableComponents; + + private readonly List drawableComponents = new List(); + private readonly DependencyContainer dependencies; + + public RoomTestDependencies() + { + SelectedRoom = new Bindable(); + RoomManager = CreateRoomManager(); + Filter = new Bindable(new FilterCriteria()); + OngoingOperationTracker = new OngoingOperationTracker(); + AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); + + dependencies = new DependencyContainer(new CachedModelDependencyContainer(null) { Model = { BindTarget = SelectedRoom } }); + + CacheAs(SelectedRoom); + CacheAs(RoomManager); + CacheAs(Filter); + CacheAs(OngoingOperationTracker); + CacheAs(AvailabilityTracker); + } + + public object Get(Type type) + => dependencies.Get(type); + + public object Get(Type type, CacheInfo info) + => dependencies.Get(type, info); + + public void Inject(T instance) + where T : class + => dependencies.Inject(instance); + + protected void Cache(object instance) + { + dependencies.Cache(instance); + if (instance is Drawable drawable) + drawableComponents.Add(drawable); + } + + protected void CacheAs(T instance) + where T : class + { + dependencies.CacheAs(instance); + if (instance is Drawable drawable) + drawableComponents.Add(drawable); + } + + protected virtual IRoomManager CreateRoomManager() => new BasicTestRoomManager(); + } +} diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs deleted file mode 100644 index d75281e772..0000000000 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomContainer.cs +++ /dev/null @@ -1,37 +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.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.Rooms; - -namespace osu.Game.Tests.Visual.OnlinePlay -{ - /// - /// Contains a single that is resolvable by components in test scenes. - /// - public class TestRoomContainer : Container - { - /// - /// The cached . - /// - public readonly Room Room = new Room(); - - public TestRoomContainer() - { - RelativeSizeAxes = Axes.Both; - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer( - new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) { Model = { Value = Room } }); - - dependencies.Cache(new Bindable(Room)); - - return dependencies; - } - } -} From 81a812e21690cc4231459765e9a4d8b859da3647 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 15:00:10 +0900 Subject: [PATCH 1050/1621] Rework MultiplayerTestScene to make use of OnlinePlayTestScene --- .../StatefulMultiplayerClientTest.cs | 8 +- .../TestSceneFreeModSelectOverlay.cs | 11 +- .../TestSceneLoungeRoomsContainer.cs | 11 +- .../TestSceneMultiSpectatorLeaderboard.cs | 85 +++++-------- .../TestSceneMultiSpectatorScreen.cs | 54 ++++---- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestSceneMultiplayerMatchSubScreen.cs | 10 +- .../TestSceneMultiplayerReadyButton.cs | 10 +- .../TestSceneMultiplayerRoomManager.cs | 120 +++++++----------- .../TestSceneMultiplayerSpectateButton.cs | 25 +--- .../MultiplayerSubScreenTestScene.cs | 17 --- .../Multiplayer/MultiplayerTestScene.cs | 55 +++----- .../TestMultiplayerRoomContainer.cs | 48 ------- .../Multiplayer/TestMultiplayerRoomManager.cs | 5 +- osu.Game/Tests/Visual/RoomTestScene.cs | 33 ----- 15 files changed, 159 insertions(+), 335 deletions(-) delete mode 100644 osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs delete mode 100644 osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs delete mode 100644 osu.Game/Tests/Visual/RoomTestScene.cs diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index adc1d6aede..0983b806e2 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Users; @@ -50,7 +51,10 @@ namespace osu.Game.Tests.NonVisual.Multiplayer AddStep("create room initially in gameplay", () => { - Room.RoomID.Value = null; + var newRoom = new Room(); + newRoom.CopyFrom(SelectedRoom.Value); + + newRoom.RoomID.Value = null; Client.RoomSetupAction = room => { room.State = MultiplayerRoomState.Playing; @@ -61,7 +65,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer }); }; - RoomManager.CreateRoom(Room); + RoomManager.CreateRoom(newRoom); }); AddUntilStep("wait for room join", () => Client.Room != null); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 25de2d0df3..26a0301d8a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -7,17 +7,14 @@ using osu.Game.Screens.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneFreeModSelectOverlay : OsuTestScene + public class TestSceneFreeModSelectOverlay : MultiplayerTestScene { [SetUp] - public void Setup() => Schedule(() => + public new void Setup() => Schedule(() => { - Child = new TestMultiplayerRoomContainer + Child = new FreeModSelectOverlay { - Child = new FreeModSelectOverlay - { - State = { Value = Visibility.Visible } - } + State = { Value = Visibility.Visible } }; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index a78cff0650..75cc687ee8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -3,7 +3,6 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Online.Rooms; @@ -22,8 +21,8 @@ namespace osu.Game.Tests.Visual.Multiplayer private RoomsContainer container; - [BackgroundDependencyLoader] - private void load() + [SetUp] + public new void Setup() => Schedule(() => { Child = container = new RoomsContainer { @@ -32,7 +31,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Width = 0.5f, JoinRequested = joinRequested }; - } + }); [Test] public void TestBasicListChanges() @@ -113,14 +112,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add rooms", () => RoomManager.AddRooms(2, new OsuRuleset().RulesetInfo)); AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo)); + // Todo: What even is this case...? + AddStep("set empty filter criteria", () => container.Filter(null)); AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5); AddStep("filter osu! rooms", () => container.Filter(new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo })); - AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2); AddStep("filter catch rooms", () => container.Filter(new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo })); - AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 9ec85d360f..54594fbfc8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -1,51 +1,48 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Database; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiSpectatorLeaderboard : OsuTestScene + public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene { - private Dictionary clocks; + public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; + protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; + + private Dictionary clocks; private MultiSpectatorLeaderboard leaderboard; - private TestContainer container; [SetUpSteps] - public void SetUpSteps() + public new void SetUpSteps() { AddStep("reset", () => { + Clear(); + clocks = new Dictionary { - { MultiplayerTestScene.PLAYER_1_ID, new ManualClock() }, - { MultiplayerTestScene.PLAYER_2_ID, new ManualClock() } + { PLAYER_1_ID, new ManualClock() }, + { PLAYER_2_ID, new ManualClock() } }; - Child = container = new TestContainer(); - foreach (var (userId, _) in clocks) - container.SpectatorClient.StartPlay(userId, 0); + SpectatorClient.StartPlay(userId, 0); }); AddStep("create leaderboard", () => @@ -55,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var scoreProcessor = new OsuScoreProcessor(); scoreProcessor.ApplyBeatmap(playable); - container.LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, container.Add); + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add); }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); @@ -76,42 +73,42 @@ namespace osu.Game.Tests.Visual.Multiplayer // For player 2, send frames in sets of 10. for (int i = 0; i < 100; i++) { - container.SpectatorClient.SendFrames(MultiplayerTestScene.PLAYER_1_ID, i, 1); + SpectatorClient.SendFrames(PLAYER_1_ID, i, 1); if (i % 10 == 0) - container.SpectatorClient.SendFrames(MultiplayerTestScene.PLAYER_2_ID, i, 10); + SpectatorClient.SendFrames(PLAYER_2_ID, i, 10); } }); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 1); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 10); + assertCombo(PLAYER_1_ID, 1); + assertCombo(PLAYER_2_ID, 10); // Advance to a point where only user player 1's frame changes. setTime(500); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 5); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 10); + assertCombo(PLAYER_1_ID, 5); + assertCombo(PLAYER_2_ID, 10); // Advance to a point where both user's frame changes. setTime(1100); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 11); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 20); + assertCombo(PLAYER_1_ID, 11); + assertCombo(PLAYER_2_ID, 20); // Advance user player 2 only to a point where its frame changes. - setTime(MultiplayerTestScene.PLAYER_2_ID, 2100); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 11); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 30); + setTime(PLAYER_2_ID, 2100); + assertCombo(PLAYER_1_ID, 11); + assertCombo(PLAYER_2_ID, 30); // Advance both users beyond their last frame setTime(101 * 100); - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 100); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 100); + assertCombo(PLAYER_1_ID, 100); + assertCombo(PLAYER_2_ID, 100); } [Test] public void TestNoFrames() { - assertCombo(MultiplayerTestScene.PLAYER_1_ID, 0); - assertCombo(MultiplayerTestScene.PLAYER_2_ID, 0); + assertCombo(PLAYER_1_ID, 0); + assertCombo(PLAYER_2_ID, 0); } private void setTime(double time) => AddStep($"set time {time}", () => @@ -126,30 +123,18 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertCombo(int userId, int expectedCombo) => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo); - private class TestContainer : TestMultiplayerRoomContainer - { - [Cached(typeof(SpectatorClient))] - public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); - [Cached(typeof(UserLookupCache))] + protected class TestDependencies : MultiplayerRoomTestDependencies + { + public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); public readonly UserLookupCache LookupCache = new TestUserLookupCache(); - protected override Container Content => content; - private readonly Container content; - - public TestContainer() + public TestDependencies() { - AddRangeInternal(new Drawable[] - { - SpectatorClient, - LookupCache, - content = new Container { RelativeSizeAxes = Axes.Both } - }); + CacheAs(SpectatorClient); + CacheAs(LookupCache); } - - public new Task LoadComponentAsync([NotNull] TLoadable component, Action onLoaded = null, CancellationToken cancellation = default, Scheduler scheduler = null) - where TLoadable : Drawable - => base.LoadComponentAsync(component, onLoaded, cancellation, scheduler); } private class TestUserLookupCache : UserLookupCache diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b91391c409..64f4b4c0d6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -15,6 +15,7 @@ using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; using osu.Game.Users; @@ -22,11 +23,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiSpectatorScreen : MultiplayerTestScene { - [Cached(typeof(SpectatorClient))] - private TestSpectatorClient spectatorClient = new TestSpectatorClient(); + public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - [Cached(typeof(UserLookupCache))] - private UserLookupCache lookupCache = new TestUserLookupCache(); + protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; [Resolved] private OsuGameBase game { get; set; } @@ -51,25 +50,12 @@ namespace osu.Game.Tests.Visual.Multiplayer importedBeatmapId = importedBeatmap.OnlineBeatmapID ?? -1; } - public override void SetUpSteps() + [SetUp] + public new void Setup() => Schedule(() => { - base.SetUpSteps(); - - AddStep("reset sent frames", () => nextFrame.Clear()); - - AddStep("add streaming client", () => - { - Remove(spectatorClient); - Add(spectatorClient); - }); - - AddStep("finish previous gameplay", () => - { - foreach (var id in playingUserIds) - spectatorClient.EndPlay(id); - playingUserIds.Clear(); - }); - } + nextFrame.Clear(); + playingUserIds.Clear(); + }); [Test] public void TestDelayedStart() @@ -87,11 +73,11 @@ namespace osu.Game.Tests.Visual.Multiplayer loadSpectateScreen(false); AddWaitStep("wait a bit", 10); - AddStep("load player first_player_id", () => spectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId)); + AddStep("load player first_player_id", () => SpectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId)); AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType().Count() == 1); AddWaitStep("wait a bit", 10); - AddStep("load player second_player_id", () => spectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId)); + AddStep("load player second_player_id", () => SpectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId)); AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType().Count() == 2); } @@ -251,7 +237,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int id in userIds) { Client.CurrentMatchPlayingUserIds.Add(id); - spectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); + SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); playingUserIds.Add(id); nextFrame[id] = 0; } @@ -262,7 +248,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("end play", () => { - spectatorClient.EndPlay(userId); + SpectatorClient.EndPlay(userId); playingUserIds.Remove(userId); nextFrame.Remove(userId); }); @@ -276,7 +262,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { foreach (int id in userIds) { - spectatorClient.SendFrames(id, nextFrame[id], count); + SpectatorClient.SendFrames(id, nextFrame[id], count); nextFrame[id] += count; } }); @@ -298,6 +284,20 @@ namespace osu.Game.Tests.Visual.Multiplayer private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + + protected class TestDependencies : MultiplayerRoomTestDependencies + { + public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); + public readonly UserLookupCache LookupCache = new TestUserLookupCache(); + + public TestDependencies() + { + CacheAs(SpectatorClient); + CacheAs(LookupCache); + } + } + internal class TestUserLookupCache : UserLookupCache { protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 2725ef5976..8bcb9cebbc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -29,7 +29,7 @@ using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerMatchSongSelect : ScreenTestScene + public class TestSceneMultiplayerMatchSongSelect : MultiplayerTestScene { private BeatmapManager manager; private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index e8ebc0c426..955be6ca21 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -49,13 +49,13 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public new void Setup() => Schedule(() => { - Room.Name.Value = "Test Room"; + SelectedRoom.Value = new Room { Name = { Value = "Test Room" } }; }); [SetUpSteps] public void SetupSteps() { - AddStep("load match", () => LoadScreen(screen = new MultiplayerMatchSubScreen(Room))); + AddStep("load match", () => LoadScreen(screen = new MultiplayerMatchSubScreen(SelectedRoom.Value))); AddUntilStep("wait for load", () => screen.IsCurrentScreen()); } @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set playlist", () => { - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - Room.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 929cd6ca80..4f2ca34fb0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -27,7 +27,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayerReadyButton : MultiplayerTestScene { private MultiplayerReadyButton button; - private OnlinePlayBeatmapAvailabilityTracker beatmapTracker; private BeatmapSetInfo importedSet; private readonly Bindable selectedItem = new Bindable(); @@ -43,18 +42,13 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); - - Add(beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker - { - SelectedItem = { BindTarget = selectedItem } - }); - - Dependencies.Cache(beatmapTracker); } [SetUp] public new void Setup() => Schedule(() => { + AvailabilityTracker.SelectedItem.BindTo(selectedItem); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); selectedItem.Value = new PlaylistItem diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index 80e36916b1..302e6998cc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -3,37 +3,38 @@ using System; using NUnit.Framework; -using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { [HeadlessTest] - public class TestSceneMultiplayerRoomManager : OsuTestScene + public class TestSceneMultiplayerRoomManager : MultiplayerTestScene { - private TestMultiplayerRoomContainer roomContainer; - private TestMultiplayerRoomManager roomManager => roomContainer.RoomManager; + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + + public TestSceneMultiplayerRoomManager() + : base(false) + { + } [Test] public void TestPollsInitially() { AddStep("create room manager with a few rooms", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom(r => r.Name.Value = "1")); - roomManager.PartRoom(); - roomManager.CreateRoom(createRoom(r => r.Name.Value = "2")); - roomManager.PartRoom(); - roomManager.ClearRooms(); - }); + RoomManager.CreateRoom(createRoom(r => r.Name.Value = "1")); + RoomManager.PartRoom(); + RoomManager.CreateRoom(createRoom(r => r.Name.Value = "2")); + RoomManager.PartRoom(); + RoomManager.ClearRooms(); }); - AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2); - AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value); + AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2); + AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value); } [Test] @@ -41,19 +42,16 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a few rooms", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - }); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); }); - AddStep("disconnect", () => roomContainer.Client.Disconnect()); + AddStep("disconnect", () => Client.Disconnect()); - AddAssert("rooms cleared", () => ((RoomManager)roomManager).Rooms.Count == 0); - AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value); + AddAssert("rooms cleared", () => ((RoomManager)RoomManager).Rooms.Count == 0); + AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value); } [Test] @@ -61,20 +59,17 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a few rooms", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - }); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); }); - AddStep("disconnect", () => roomContainer.Client.Disconnect()); - AddStep("connect", () => roomContainer.Client.Connect()); + AddStep("disconnect", () => Client.Disconnect()); + AddStep("connect", () => Client.Connect()); - AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2); - AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value); + AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2); + AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value); } [Test] @@ -82,15 +77,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a room", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - roomManager.ClearRooms(); - }); + RoomManager.CreateRoom(createRoom()); + RoomManager.ClearRooms(); }); - AddAssert("manager not polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 0); - AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value); + AddAssert("manager not polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 0); + AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value); } [Test] @@ -98,13 +90,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a room", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - }); + RoomManager.CreateRoom(createRoom()); }); - AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null); + AddUntilStep("multiplayer room joined", () => Client.Room != null); } [Test] @@ -112,14 +101,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a room", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - roomManager.CreateRoom(createRoom()); - roomManager.PartRoom(); - }); + RoomManager.CreateRoom(createRoom()); + RoomManager.PartRoom(); }); - AddAssert("multiplayer room parted", () => roomContainer.Client.Room == null); + AddAssert("multiplayer room parted", () => Client.Room == null); } [Test] @@ -127,16 +113,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create room manager with a room", () => { - createRoomManager().With(d => d.OnLoadComplete += _ => - { - var r = createRoom(); - roomManager.CreateRoom(r); - roomManager.PartRoom(); - roomManager.JoinRoom(r); - }); + var r = createRoom(); + RoomManager.CreateRoom(r); + RoomManager.PartRoom(); + RoomManager.JoinRoom(r); }); - AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null); + AddUntilStep("multiplayer room joined", () => Client.Room != null); } private Room createRoom(Action initFunc = null) @@ -161,18 +144,13 @@ namespace osu.Game.Tests.Visual.Multiplayer return room; } - private TestMultiplayerRoomManager createRoomManager() + private class TestDependencies : MultiplayerRoomTestDependencies { - Child = roomContainer = new TestMultiplayerRoomContainer + public TestDependencies() { - RoomManager = - { - TimeBetweenListingPolls = { Value = 1 }, - TimeBetweenSelectionPolls = { Value = 1 } - } - }; - - return roomManager; + RoomManager.TimeBetweenListingPolls.Value = 1; + RoomManager.TimeBetweenSelectionPolls.Value = 1; + } } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index d00404102c..070158f552 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -37,40 +37,19 @@ namespace osu.Game.Tests.Visual.Multiplayer private IDisposable readyClickOperation; - protected override Container Content => content; - private readonly Container content; - - public TestSceneMultiplayerSpectateButton() - { - base.Content.Add(content = new Container - { - RelativeSizeAxes = Axes.Both - }); - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - return dependencies; - } - [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - - var beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } }; - base.Content.Add(beatmapTracker); - Dependencies.Cache(beatmapTracker); - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); } [SetUp] public new void Setup() => Schedule(() => { + AvailabilityTracker.SelectedItem.BindTo(selectedItem); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); selectedItem.Value = new PlaylistItem diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs deleted file mode 100644 index 5c405ee9d3..0000000000 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerSubScreenTestScene.cs +++ /dev/null @@ -1,17 +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.Tests.Visual.OnlinePlay; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public class MultiplayerSubScreenTestScene : OnlinePlayTestScene, IMultiplayerRoomTestDependencies - { - public TestMultiplayerClient Client => RoomDependencies.Client; - public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; - - protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; - - protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); - } -} diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index c76d1053b2..69dfd41e04 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -2,66 +2,51 @@ // 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; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay; -using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public abstract class MultiplayerTestScene : RoomTestScene + public abstract class MultiplayerTestScene : OnlinePlayTestScene { public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; - [Cached(typeof(MultiplayerClient))] - public TestMultiplayerClient Client { get; } + public TestMultiplayerClient Client => RoomDependencies.Client; + public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; - [Cached(typeof(IRoomManager))] - public TestMultiplayerRoomManager RoomManager { get; } + protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; - [Cached] - public Bindable Filter { get; } - - [Cached] - public OngoingOperationTracker OngoingOperationTracker { get; } - - protected override Container Content => content; - private readonly TestMultiplayerRoomContainer content; + protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); private readonly bool joinRoom; protected MultiplayerTestScene(bool joinRoom = true) { this.joinRoom = joinRoom; - base.Content.Add(content = new TestMultiplayerRoomContainer { RelativeSizeAxes = Axes.Both }); - - Client = content.Client; - RoomManager = content.RoomManager; - Filter = content.Filter; - OngoingOperationTracker = content.OngoingOperationTracker; } [SetUp] public new void Setup() => Schedule(() => { - RoomManager.Schedule(() => RoomManager.PartRoom()); - if (joinRoom) { - Room.Name.Value = "test name"; - Room.Playlist.Add(new PlaylistItem + var room = new Room { - Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, - Ruleset = { Value = Ruleset.Value } - }); + Name = { Value = "test name" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, + Ruleset = { Value = Ruleset.Value } + } + } + }; - RoomManager.Schedule(() => RoomManager.CreateRoom(Room)); + RoomManager.CreateRoom(room); + SelectedRoom.Value = room; } }); @@ -70,7 +55,9 @@ namespace osu.Game.Tests.Visual.Multiplayer base.SetUpSteps(); if (joinRoom) + { AddUntilStep("wait for room join", () => Client.Room != null); + } } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs deleted file mode 100644 index 1abf4d8f5d..0000000000 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs +++ /dev/null @@ -1,48 +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.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.Multiplayer; -using osu.Game.Screens.OnlinePlay; -using osu.Game.Screens.OnlinePlay.Lounge.Components; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public class TestMultiplayerRoomContainer : Container - { - protected override Container Content => content; - private readonly Container content; - - [Cached(typeof(MultiplayerClient))] - public readonly TestMultiplayerClient Client; - - [Cached(typeof(IRoomManager))] - public readonly TestMultiplayerRoomManager RoomManager; - - [Cached] - public readonly Bindable Filter = new Bindable(new FilterCriteria()); - - [Cached] - public readonly OngoingOperationTracker OngoingOperationTracker; - - public TestMultiplayerRoomContainer() - { - RelativeSizeAxes = Axes.Both; - - RoomManager = new TestMultiplayerRoomManager(); - Client = new TestMultiplayerClient(RoomManager); - OngoingOperationTracker = new OngoingOperationTracker(); - - AddRangeInternal(new Drawable[] - { - Client, - RoomManager, - OngoingOperationTracker, - content = new Container { RelativeSizeAxes = Axes.Both } - }); - } - } -} diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 315be510a3..6f4a464d57 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -29,10 +29,9 @@ namespace osu.Game.Tests.Visual.Multiplayer public new readonly List Rooms = new List(); - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - int currentScoreId = 0; int currentRoomId = 0; int currentPlaylistItemId = 0; diff --git a/osu.Game/Tests/Visual/RoomTestScene.cs b/osu.Game/Tests/Visual/RoomTestScene.cs deleted file mode 100644 index aaf5c7624f..0000000000 --- a/osu.Game/Tests/Visual/RoomTestScene.cs +++ /dev/null @@ -1,33 +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.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Online.Rooms; - -namespace osu.Game.Tests.Visual -{ - public abstract class RoomTestScene : ScreenTestScene - { - [Cached] - private readonly Bindable currentRoom = new Bindable(); - - protected Room Room => currentRoom.Value; - - private CachedModelDependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Model.BindTo(currentRoom); - return dependencies; - } - - [SetUp] - public void Setup() => Schedule(() => - { - currentRoom.Value = new Room(); - }); - } -} From 57ae87956a54e7ac3365abde44338a04f5d33a45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Jun 2021 15:27:40 +0900 Subject: [PATCH 1051/1621] Update execution state change blocking logic in line with framework changes --- osu.Game/OsuGameBase.cs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d0be03f8c1..dec738e5b3 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -24,6 +24,7 @@ using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Logging; +using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Database; using osu.Game.Input; @@ -156,6 +157,8 @@ namespace osu.Game private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(GLOBAL_TRACK_VOLUME_ADJUST); + private IBindable updateThreadState; + public OsuGameBase() { UseDevelopmentServer = DebugUtils.IsDebugBuild; @@ -183,7 +186,8 @@ namespace osu.Game dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); - Host.UpdateThread.ThreadPausing += onUpdateThreadPausing; + updateThreadState = Host.UpdateThread.State.GetBoundCopy(); + updateThreadState.BindValueChanged(updateThreadStateChanged); AddInternal(realmFactory); @@ -359,10 +363,21 @@ namespace osu.Game Ruleset.BindValueChanged(onRulesetChanged); } - private void onUpdateThreadPausing() + private IDisposable blocking; + + private void updateThreadStateChanged(ValueChangedEvent state) { - var blocking = realmFactory.BlockAllOperations(); - Schedule(() => blocking.Dispose()); + switch (state.NewValue) + { + case GameThreadState.Running: + blocking.Dispose(); + blocking = null; + break; + + case GameThreadState.Paused: + blocking = realmFactory.BlockAllOperations(); + break; + } } protected override void LoadComplete() @@ -498,9 +513,6 @@ namespace osu.Game LocalConfig?.Dispose(); contextFactory.FlushConnections(); - - if (Host?.UpdateThread != null) - Host.UpdateThread.ThreadPausing -= onUpdateThreadPausing; } } } From d6ab08c95821927001a863ad2db40199fc8656a3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 15:30:23 +0900 Subject: [PATCH 1052/1621] Remove manual dependencies in TestSceneMultiplayerGameplayLeaderboard --- ...TestSceneMultiplayerGameplayLeaderboard.cs | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index af2f6fa5fe..9c30151f05 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -7,7 +7,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Configuration; @@ -20,6 +19,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Tests.Visual.Online; +using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer @@ -28,28 +28,15 @@ namespace osu.Game.Tests.Visual.Multiplayer { private const int users = 16; - [Cached(typeof(SpectatorClient))] - private TestMultiplayerSpectatorClient spectatorClient = new TestMultiplayerSpectatorClient(); + public TestMultiplayerSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - [Cached(typeof(UserLookupCache))] - private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache(); + public UserLookupCache LookupCache => RoomDependencies?.LookupCache; + + protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; private MultiplayerGameplayLeaderboard leaderboard; - - protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; - private OsuConfigManager config; - public TestSceneMultiplayerGameplayLeaderboard() - { - base.Content.Children = new Drawable[] - { - spectatorClient, - lookupCache, - Content - }; - } - [BackgroundDependencyLoader] private void load() { @@ -59,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUpSteps] public override void SetUpSteps() { - AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = lookupCache.GetUserAsync(1).Result); + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result); AddStep("create leaderboard", () => { @@ -71,12 +58,12 @@ namespace osu.Game.Tests.Visual.Multiplayer var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); for (int i = 0; i < users; i++) - spectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + SpectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); - spectatorClient.Schedule(() => + SpectatorClient.Schedule(() => { Client.CurrentMatchPlayingUserIds.Clear(); - Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers); + Client.CurrentMatchPlayingUserIds.AddRange(SpectatorClient.PlayingUsers); }); Children = new Drawable[] @@ -86,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, spectatorClient.PlayingUsers.ToArray()) + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, SpectatorClient.PlayingUsers.ToArray()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -100,7 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestScoreUpdates() { - AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 100); + AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 100); AddToggleStep("switch compact mode", expanded => leaderboard.Expanded.Value = expanded); } @@ -113,11 +100,25 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestChangeScoringMode() { - AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 5); + AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 5); AddStep("change to classic", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Classic)); AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised)); } + protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + + protected class TestDependencies : MultiplayerRoomTestDependencies + { + public readonly TestMultiplayerSpectatorClient SpectatorClient = new TestMultiplayerSpectatorClient(); + public readonly UserLookupCache LookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache(); + + public TestDependencies() + { + CacheAs(SpectatorClient); + CacheAs(LookupCache); + } + } + public class TestMultiplayerSpectatorClient : TestSpectatorClient { private readonly Dictionary lastHeaders = new Dictionary(); From 8241fee4a8aab8853d11853ca0757e08e13a3664 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 09:22:34 +0300 Subject: [PATCH 1053/1621] Add failing test case --- .../TestSceneRulesetSkinProvidingContainer.cs | 100 ++++++++++++++++++ .../Testing/TestSceneRulesetDependencies.cs | 2 +- 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs new file mode 100644 index 0000000000..dd78f28351 --- /dev/null +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -0,0 +1,100 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. + // See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using JetBrains.Annotations; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Skinning; +using osu.Game.Tests.Testing; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Rulesets +{ + public class TestSceneRulesetSkinProvidingContainer : OsuTestScene + { + [Resolved] + private SkinManager skins { get; set; } + + private SkinRequester requester; + + protected override Ruleset CreateRuleset() => new TestRuleset(); + + [Test] + public void TestEarlyAddedSkinRequester() + { + ISample transformerSampleOnBdl = null; + + // need a legacy skin to plug the TestRuleset's legacy transformer, which is required for testing this. + AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); + + AddStep("setup provider", () => + { + var rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin); + + rulesetSkinProvider.Add(requester = new SkinRequester()); + + requester.OnBdl += () => transformerSampleOnBdl = requester.GetSample(new SampleInfo(TestLegacySkinTransformer.VIRTUAL_SAMPLE_NAME)); + + Child = rulesetSkinProvider; + }); + + AddAssert("requester got correct initial sample", () => transformerSampleOnBdl != null); + } + + private class SkinRequester : Drawable, ISkin + { + private ISkinSource skin; + + public event Action OnBdl; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + this.skin = skin; + + OnBdl?.Invoke(); + } + + public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component); + + public Texture GetTexture(string componentName, WrapMode wrapModeS = default, WrapMode wrapModeT = default) => skin.GetTexture(componentName); + + public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); + + public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); + } + + private class TestRuleset : TestSceneRulesetDependencies.TestRuleset + { + public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TestLegacySkinTransformer(skin); + } + + private class TestLegacySkinTransformer : LegacySkinTransformer + { + public const string VIRTUAL_SAMPLE_NAME = "virtual-test-sample"; + + public TestLegacySkinTransformer([NotNull] ISkin skin) + : base(skin) + { + } + + public override ISample GetSample(ISampleInfo sampleInfo) + { + if (sampleInfo.LookupNames.Single() == VIRTUAL_SAMPLE_NAME) + return new SampleVirtual(); + + return base.GetSample(sampleInfo); + } + } + } +} diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs index 97087e31ab..fb50da32f3 100644 --- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs +++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Testing Dependencies.Get() != null); } - private class TestRuleset : Ruleset + public class TestRuleset : Ruleset { public override string Description => string.Empty; public override string ShortName => string.Empty; From f07008a0a284023e50f63e7de3c2392d2de0e626 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 09:55:29 +0300 Subject: [PATCH 1054/1621] Fix `RulesetSkinProvidingContainer` potentially late in setting up skin sources --- .../Skinning/RulesetSkinProvidingContainer.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index c48aeca99a..abf5cb040a 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -42,27 +42,30 @@ namespace osu.Game.Skinning }; } - [Resolved] - private ISkinSource skinSource { get; set; } + private ISkinSource parentSource; - [BackgroundDependencyLoader] - private void load() + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - UpdateSkins(); - skinSource.SourceChanged += OnSourceChanged; + parentSource = parent.Get(); + + UpdateSkinSources(); + + parentSource.SourceChanged += OnSourceChanged; + + return base.CreateChildDependencies(parent); } protected override void OnSourceChanged() { - UpdateSkins(); + UpdateSkinSources(); base.OnSourceChanged(); } - protected virtual void UpdateSkins() + protected virtual void UpdateSkinSources() { SkinSources.Clear(); - foreach (var skin in skinSource.AllSources) + foreach (var skin in parentSource.AllSources) { switch (skin) { @@ -93,8 +96,8 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - if (skinSource != null) - skinSource.SourceChanged -= OnSourceChanged; + if (parentSource != null) + parentSource.SourceChanged -= OnSourceChanged; } } } From 58839221771cf68b5d95fc5101befb3bdf70250f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Jun 2021 16:36:31 +0900 Subject: [PATCH 1055/1621] Remove mod multiplier completely --- .../TestSceneModSelectOverlay.cs | 22 +--------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 42 +------------------ .../OnlinePlay/FreeModSelectOverlay.cs | 1 - 3 files changed, 3 insertions(+), 62 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 2885dbee00..df8ef92a05 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; @@ -107,7 +106,6 @@ namespace osu.Game.Tests.Visual.UserInterface var conversionMods = osu.GetModsFor(ModType.Conversion); var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); - var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); @@ -120,8 +118,6 @@ namespace osu.Game.Tests.Visual.UserInterface testMultiMod(doubleTimeMod); testIncompatibleMods(easy, hardRock); testDeselectAll(easierMods.Where(m => !(m is MultiMod))); - testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour); - testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour); testUnimplementedMod(targetMod); } @@ -149,7 +145,7 @@ namespace osu.Game.Tests.Visual.UserInterface changeRuleset(0); - AddAssert("ensure mods still selected", () => modDisplay.Current.Value.Single(m => m is OsuModNoFail) != null); + AddAssert("ensure mods still selected", () => modDisplay.Current.Value.SingleOrDefault(m => m is OsuModNoFail) != null); changeRuleset(3); @@ -316,17 +312,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any()); } - private void testMultiplierTextColour(Mod mod, Func getCorrectColour) - { - checkLabelColor(() => Color4.White); - selectNext(mod); - AddWaitStep("wait for changing colour", 1); - checkLabelColor(getCorrectColour); - selectPrevious(mod); - AddWaitStep("wait for changing colour", 1); - checkLabelColor(() => Color4.White); - } - private void testModsWithSameBaseType(Mod modA, Mod modB) { selectNext(modA); @@ -348,7 +333,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert($"check {mod.Name} is selected", () => { var button = modSelect.GetModButton(mod); - return modSelect.SelectedMods.Value.Single(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; + return modSelect.SelectedMods.Value.SingleOrDefault(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; }); } @@ -370,8 +355,6 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private void checkLabelColor(Func getColour) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == getColour()); - private void createDisplay(Func createOverlayFunc) { Children = new Drawable[] @@ -408,7 +391,6 @@ namespace osu.Game.Tests.Visual.UserInterface return section.ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); } - public new OsuSpriteText MultiplierLabel => base.MultiplierLabel; public new TriangleButton DeselectAllButton => base.DeselectAllButton; public new Color4 LowMultiplierColour => base.LowMultiplierColour; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 85dcade986..e4aab978fc 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -37,9 +37,6 @@ namespace osu.Game.Overlays.Mods protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CloseButton; - protected readonly Drawable MultiplierSection; - protected readonly OsuSpriteText MultiplierLabel; - protected readonly FillFlowContainer FooterContainer; protected override bool BlockNonPositionalInput => false; @@ -324,20 +321,6 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, - MultiplierSection = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(footer_button_spacing / 2, 0), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Child = MultiplierLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Width = 70, // make width fixed so reflow doesn't occur when multiplier number changes. - }, - }, } } }, @@ -351,11 +334,8 @@ namespace osu.Game.Overlays.Mods } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, AudioManager audio, OsuGameBase osu) + private void load(AudioManager audio, OsuGameBase osu) { - LowMultiplierColour = colours.Red; - HighMultiplierColour = colours.Green; - availableMods = osu.AvailableMods.GetBoundCopy(); sampleOn = audio.Samples.Get(@"UI/check-on"); @@ -485,26 +465,6 @@ namespace osu.Game.Overlays.Mods foreach (var section in ModSectionsContainer.Children) section.UpdateSelectedButtons(selectedMods); - - updateMultiplier(); - } - - private void updateMultiplier() - { - var multiplier = 1.0; - - foreach (var mod in SelectedMods.Value) - { - multiplier *= mod.ScoreMultiplier; - } - - MultiplierLabel.Text = $"{multiplier:N2}x"; - if (multiplier > 1.0) - MultiplierLabel.FadeColour(HighMultiplierColour, 200); - else if (multiplier < 1.0) - MultiplierLabel.FadeColour(LowMultiplierColour, 200); - else - MultiplierLabel.FadeColour(Color4.White, 200); } private void modButtonPressed(Mod selectedMod) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 5e2e9fd087..d5abaaab4e 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -32,7 +32,6 @@ namespace osu.Game.Screens.OnlinePlay { IsValidMod = m => true; - MultiplierSection.Alpha = 0; DeselectAllButton.Alpha = 0; Drawable selectAllButton; From 06e357647abce9f383d0dee2c66bfc85440a41e7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 10:40:07 +0300 Subject: [PATCH 1056/1621] OnBdl -> OnLoadAsync --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index dd78f28351..3072c466e0 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Rulesets [Test] public void TestEarlyAddedSkinRequester() { - ISample transformerSampleOnBdl = null; + ISample transformerSampleOnLoad = null; // need a legacy skin to plug the TestRuleset's legacy transformer, which is required for testing this. AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); @@ -43,26 +43,26 @@ namespace osu.Game.Tests.Rulesets rulesetSkinProvider.Add(requester = new SkinRequester()); - requester.OnBdl += () => transformerSampleOnBdl = requester.GetSample(new SampleInfo(TestLegacySkinTransformer.VIRTUAL_SAMPLE_NAME)); + requester.OnLoadAsync += () => transformerSampleOnLoad = requester.GetSample(new SampleInfo(TestLegacySkinTransformer.VIRTUAL_SAMPLE_NAME)); Child = rulesetSkinProvider; }); - AddAssert("requester got correct initial sample", () => transformerSampleOnBdl != null); + AddAssert("requester got correct initial sample", () => transformerSampleOnLoad != null); } private class SkinRequester : Drawable, ISkin { private ISkinSource skin; - public event Action OnBdl; + public event Action OnLoadAsync; [BackgroundDependencyLoader] private void load(ISkinSource skin) { this.skin = skin; - OnBdl?.Invoke(); + OnLoadAsync?.Invoke(); } public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component); From 8d7705dc923b04ea037803260fc40fbc02f55933 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 10:55:23 +0300 Subject: [PATCH 1057/1621] Test using a simple `GetTexture` lookup instead Presumes that `RulesetSkinProvidingContainer` doesn't allow falling back to parent skins, whatsoever. --- .../TestSceneRulesetSkinProvidingContainer.cs | 37 +++---------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 3072c466e0..50b75ea0c5 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -12,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; -using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Skinning; using osu.Game.Tests.Testing; @@ -27,15 +24,14 @@ namespace osu.Game.Tests.Rulesets private SkinRequester requester; - protected override Ruleset CreateRuleset() => new TestRuleset(); + protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); [Test] public void TestEarlyAddedSkinRequester() { - ISample transformerSampleOnLoad = null; + Texture textureOnLoad = null; - // need a legacy skin to plug the TestRuleset's legacy transformer, which is required for testing this. - AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); + AddStep("set skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); AddStep("setup provider", () => { @@ -43,12 +39,12 @@ namespace osu.Game.Tests.Rulesets rulesetSkinProvider.Add(requester = new SkinRequester()); - requester.OnLoadAsync += () => transformerSampleOnLoad = requester.GetSample(new SampleInfo(TestLegacySkinTransformer.VIRTUAL_SAMPLE_NAME)); + requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture("hitcircle"); Child = rulesetSkinProvider; }); - AddAssert("requester got correct initial sample", () => transformerSampleOnLoad != null); + AddAssert("requester got correct initial texture", () => textureOnLoad != null); } private class SkinRequester : Drawable, ISkin @@ -73,28 +69,5 @@ namespace osu.Game.Tests.Rulesets public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); } - - private class TestRuleset : TestSceneRulesetDependencies.TestRuleset - { - public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TestLegacySkinTransformer(skin); - } - - private class TestLegacySkinTransformer : LegacySkinTransformer - { - public const string VIRTUAL_SAMPLE_NAME = "virtual-test-sample"; - - public TestLegacySkinTransformer([NotNull] ISkin skin) - : base(skin) - { - } - - public override ISample GetSample(ISampleInfo sampleInfo) - { - if (sampleInfo.LookupNames.Single() == VIRTUAL_SAMPLE_NAME) - return new SampleVirtual(); - - return base.GetSample(sampleInfo); - } - } } } From 13ed52a990cc4f1b53b3a1f1b37694859b5427cf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 11:16:26 +0300 Subject: [PATCH 1058/1621] Fix weird license misindent No idea how the hell that happened... R# silent about it, of course. --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 50b75ea0c5..b6800e40e4 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -1,5 +1,5 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. - // See the LICENCE file in the repository root for full licence text. +// See the LICENCE file in the repository root for full licence text. using System; using NUnit.Framework; From 7aefbe3da16c3abecb1cfa172f4aa18a0bb329a7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 17:37:02 +0900 Subject: [PATCH 1059/1621] Move UserLookupCache inside dependencies --- .../Visual/Gameplay/TestSceneSpectator.cs | 11 -------- .../TestSceneMultiSpectatorLeaderboard.cs | 18 ------------- .../TestSceneMultiSpectatorScreen.cs | 18 ------------- ...TestSceneMultiplayerGameplayLeaderboard.cs | 25 +++++++------------ ...ies.cs => IMultiplayerTestDependencies.cs} | 8 +++++- .../MultiplayerRoomTestDependencies.cs | 7 +++++- .../Multiplayer/MultiplayerTestScene.cs | 3 ++- ...cies.cs => IOnlinePlayTestDependencies.cs} | 2 +- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 2 +- .../Visual/OnlinePlay/RoomTestDependencies.cs | 2 +- osu.Game/Tests/Visual/TestUserLookupCache.cs | 19 ++++++++++++++ 11 files changed, 46 insertions(+), 69 deletions(-) rename osu.Game/Tests/Visual/Multiplayer/{IMultiplayerRoomTestDependencies.cs => IMultiplayerTestDependencies.cs} (71%) rename osu.Game/Tests/Visual/OnlinePlay/{IRoomTestDependencies.cs => IOnlinePlayTestDependencies.cs} (95%) create mode 100644 osu.Game/Tests/Visual/TestUserLookupCache.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 6eeb3596a8..7f3056771d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using System.Threading; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -232,14 +230,5 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("load screen", () => LoadScreen(spectatorScreen = new SoloSpectator(streamingUser))); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded); } - - internal class TestUserLookupCache : UserLookupCache - { - protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) => Task.FromResult(new User - { - Id = lookup, - Username = $"User {lookup}" - }); - } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 54594fbfc8..7f586009a1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -3,19 +3,15 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Game.Database; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { @@ -128,24 +124,10 @@ namespace osu.Game.Tests.Visual.Multiplayer protected class TestDependencies : MultiplayerRoomTestDependencies { public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); - public readonly UserLookupCache LookupCache = new TestUserLookupCache(); public TestDependencies() { CacheAs(SpectatorClient); - CacheAs(LookupCache); - } - } - - private class TestUserLookupCache : UserLookupCache - { - protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) - { - return Task.FromResult(new User - { - Id = lookup, - Username = $"User {lookup}" - }); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 64f4b4c0d6..e6634a598e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -3,21 +3,17 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { @@ -289,24 +285,10 @@ namespace osu.Game.Tests.Visual.Multiplayer protected class TestDependencies : MultiplayerRoomTestDependencies { public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); - public readonly UserLookupCache LookupCache = new TestUserLookupCache(); public TestDependencies() { CacheAs(SpectatorClient); - CacheAs(LookupCache); - } - } - - internal class TestUserLookupCache : UserLookupCache - { - protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) - { - return Task.FromResult(new User - { - Id = lookup, - Username = $"User {lookup}" - }); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 9c30151f05..ae0938ae37 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -6,11 +6,11 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Configuration; -using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; @@ -18,7 +18,6 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; -using osu.Game.Tests.Visual.Online; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; @@ -26,12 +25,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerTestScene { - private const int users = 16; + private static IEnumerable users => Enumerable.Range(0, 16); public TestMultiplayerSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - public UserLookupCache LookupCache => RoomDependencies?.LookupCache; - protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; private MultiplayerGameplayLeaderboard leaderboard; @@ -57,14 +54,11 @@ namespace osu.Game.Tests.Visual.Multiplayer var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); - for (int i = 0; i < users; i++) - SpectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + foreach (var user in users) + SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); - SpectatorClient.Schedule(() => - { - Client.CurrentMatchPlayingUserIds.Clear(); - Client.CurrentMatchPlayingUserIds.AddRange(SpectatorClient.PlayingUsers); - }); + // Todo: This is REALLY bad. + Client.CurrentMatchPlayingUserIds.AddRange(users); Children = new Drawable[] { @@ -73,7 +67,7 @@ namespace osu.Game.Tests.Visual.Multiplayer scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, SpectatorClient.PlayingUsers.ToArray()) + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, users.ToArray()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -94,7 +88,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestUserQuit() { - AddRepeatStep("mark user quit", () => Client.CurrentMatchPlayingUserIds.RemoveAt(0), users); + foreach (var user in users) + AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).Result.AsNonNull())); } [Test] @@ -110,12 +105,10 @@ namespace osu.Game.Tests.Visual.Multiplayer protected class TestDependencies : MultiplayerRoomTestDependencies { public readonly TestMultiplayerSpectatorClient SpectatorClient = new TestMultiplayerSpectatorClient(); - public readonly UserLookupCache LookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache(); public TestDependencies() { CacheAs(SpectatorClient); - CacheAs(LookupCache); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs similarity index 71% rename from osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs rename to osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs index 0b53bdd04e..331c4dcba6 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs @@ -1,13 +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 osu.Game.Database; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public interface IMultiplayerRoomTestDependencies : IRoomTestDependencies + public interface IMultiplayerTestDependencies : IOnlinePlayTestDependencies { /// /// The cached . @@ -18,5 +19,10 @@ namespace osu.Game.Tests.Visual.Multiplayer /// The cached . /// new TestMultiplayerRoomManager RoomManager { get; } + + /// + /// The cached . + /// + TestUserLookupCache LookupCache { get; } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs index e3913dd291..369563c5d9 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -1,21 +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.Game.Database; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerRoomTestDependencies + public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerTestDependencies { public TestMultiplayerClient Client { get; } + public TestUserLookupCache LookupCache { get; } public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; public MultiplayerRoomTestDependencies() { Client = new TestMultiplayerClient(RoomManager); + LookupCache = new TestUserLookupCache(); + CacheAs(Client); + CacheAs(LookupCache); } protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 69dfd41e04..5c717bfb2f 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -8,13 +8,14 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public abstract class MultiplayerTestScene : OnlinePlayTestScene + public abstract class MultiplayerTestScene : OnlinePlayTestScene, IMultiplayerTestDependencies { public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; public TestMultiplayerClient Client => RoomDependencies.Client; public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; + public TestUserLookupCache LookupCache => RoomDependencies?.LookupCache; protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; diff --git a/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs similarity index 95% rename from osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs rename to osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs index 848e2aa77f..a4e0368adc 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs @@ -8,7 +8,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { - public interface IRoomTestDependencies + public interface IOnlinePlayTestDependencies { /// /// The cached . diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 5e46ea0544..39ce219092 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// A providing all the dependencies cached by for testing s. /// - public abstract class OnlinePlayTestScene : ScreenTestScene, IRoomTestDependencies + public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestDependencies { public Bindable SelectedRoom => RoomDependencies?.SelectedRoom; public IRoomManager RoomManager => RoomDependencies?.RoomManager; diff --git a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs index 9d22f9e286..b833a9400f 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Contains dependencies for testing online-play rooms. /// - public class RoomTestDependencies : IReadOnlyDependencyContainer, IRoomTestDependencies + public class RoomTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies { public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } diff --git a/osu.Game/Tests/Visual/TestUserLookupCache.cs b/osu.Game/Tests/Visual/TestUserLookupCache.cs new file mode 100644 index 0000000000..d2941b5bd5 --- /dev/null +++ b/osu.Game/Tests/Visual/TestUserLookupCache.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 System.Threading; +using System.Threading.Tasks; +using osu.Game.Database; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + public class TestUserLookupCache : UserLookupCache + { + protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) => Task.FromResult(new User + { + Id = lookup, + Username = $"User {lookup}" + }); + } +} From c0d2b41d4c54fa861b9945dd9b26795b064478da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 17:55:16 +0900 Subject: [PATCH 1060/1621] Move SpectatorClient into multiplayer dependencies --- .../TestSceneMultiSpectatorLeaderboard.cs | 19 ------------------- .../TestSceneMultiSpectatorScreen.cs | 19 ------------------- ...TestSceneMultiplayerGameplayLeaderboard.cs | 11 ++--------- .../TestSceneMultiplayerRoomManager.cs | 1 + .../IMultiplayerTestDependencies.cs | 6 ++++++ .../MultiplayerRoomTestDependencies.cs | 8 ++++++++ .../Multiplayer/MultiplayerTestScene.cs | 2 ++ 7 files changed, 19 insertions(+), 47 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 7f586009a1..2537198503 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -6,21 +6,14 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Game.Online.Spectator; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; -using osu.Game.Tests.Visual.OnlinePlay; -using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene { - public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - - protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; - private Dictionary clocks; private MultiSpectatorLeaderboard leaderboard; @@ -118,17 +111,5 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertCombo(int userId, int expectedCombo) => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo); - - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); - - protected class TestDependencies : MultiplayerRoomTestDependencies - { - public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); - - public TestDependencies() - { - CacheAs(SpectatorClient); - } - } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index e6634a598e..873f8ca35b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -8,21 +8,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; -using osu.Game.Tests.Visual.OnlinePlay; -using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiSpectatorScreen : MultiplayerTestScene { - public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - - protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; - [Resolved] private OsuGameBase game { get; set; } @@ -279,17 +272,5 @@ namespace osu.Game.Tests.Visual.Multiplayer private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single(); private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); - - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); - - protected class TestDependencies : MultiplayerRoomTestDependencies - { - public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); - - public TestDependencies() - { - CacheAs(SpectatorClient); - } - } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index ae0938ae37..b7ccc17397 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -27,9 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { private static IEnumerable users => Enumerable.Range(0, 16); - public TestMultiplayerSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; - - protected new TestDependencies RoomDependencies => (TestDependencies)base.RoomDependencies; + public new TestMultiplayerSpectatorClient SpectatorClient => (TestMultiplayerSpectatorClient)RoomDependencies?.SpectatorClient; private MultiplayerGameplayLeaderboard leaderboard; private OsuConfigManager config; @@ -104,12 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer protected class TestDependencies : MultiplayerRoomTestDependencies { - public readonly TestMultiplayerSpectatorClient SpectatorClient = new TestMultiplayerSpectatorClient(); - - public TestDependencies() - { - CacheAs(SpectatorClient); - } + protected override TestSpectatorClient CreateSpectatorClient() => new TestMultiplayerSpectatorClient(); } public class TestMultiplayerSpectatorClient : TestSpectatorClient diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index 302e6998cc..225a9ff703 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -148,6 +148,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public TestDependencies() { + // Need to set these values as early as possible. RoomManager.TimeBetweenListingPolls.Value = 1; RoomManager.TimeBetweenSelectionPolls.Value = 1; } diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs index 331c4dcba6..c5f9e85003 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs @@ -5,6 +5,7 @@ using osu.Game.Database; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; +using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { @@ -24,5 +25,10 @@ namespace osu.Game.Tests.Visual.Multiplayer /// The cached . /// TestUserLookupCache LookupCache { get; } + + /// + /// The cached . + /// + TestSpectatorClient SpectatorClient { get; } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs index 369563c5d9..a3f7156848 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -3,8 +3,10 @@ using osu.Game.Database; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; +using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { @@ -12,17 +14,23 @@ namespace osu.Game.Tests.Visual.Multiplayer { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } + public TestSpectatorClient SpectatorClient { get; } + public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; public MultiplayerRoomTestDependencies() { Client = new TestMultiplayerClient(RoomManager); LookupCache = new TestUserLookupCache(); + SpectatorClient = CreateSpectatorClient(); CacheAs(Client); CacheAs(LookupCache); + CacheAs(SpectatorClient); } protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); + + protected virtual TestSpectatorClient CreateSpectatorClient() => new TestSpectatorClient(); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 5c717bfb2f..19e7536286 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Game.Online.Rooms; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.OnlinePlay; +using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { @@ -16,6 +17,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestMultiplayerClient Client => RoomDependencies.Client; public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; public TestUserLookupCache LookupCache => RoomDependencies?.LookupCache; + public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; From 04279e7f5cbfa0a90982f4cfb333be8f0e99f4b0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 18:02:53 +0900 Subject: [PATCH 1061/1621] Add some xmldocs --- .../Visual/Multiplayer/IMultiplayerTestDependencies.cs | 3 +++ .../Multiplayer/MultiplayerRoomTestDependencies.cs | 4 +++- .../Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 9 +++++---- .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 3 +++ .../Visual/Multiplayer/TestMultiplayerRoomManager.cs | 4 ++++ osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs | 4 ++++ .../Visual/OnlinePlay/IOnlinePlayTestDependencies.cs | 3 +++ osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 2 +- osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs | 2 +- 9 files changed, 27 insertions(+), 7 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs index c5f9e85003..46ad5a5a1c 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs @@ -9,6 +9,9 @@ using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// Interface that defines the dependencies required for multiplayer test scenes. + /// public interface IMultiplayerTestDependencies : IOnlinePlayTestDependencies { /// diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs index a3f7156848..4c5afb9b58 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -10,12 +10,14 @@ using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// Contains the basic dependencies of multiplayer test scenes. + /// public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerTestDependencies { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } public TestSpectatorClient SpectatorClient { get; } - public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; public MultiplayerRoomTestDependencies() diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 19e7536286..6a9ae4c772 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -9,6 +9,9 @@ using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// The base test scene for all multiplayer components and screens. + /// public abstract class MultiplayerTestScene : OnlinePlayTestScene, IMultiplayerTestDependencies { public const int PLAYER_1_ID = 55; @@ -21,8 +24,6 @@ namespace osu.Game.Tests.Visual.Multiplayer protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; - protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); - private readonly bool joinRoom; protected MultiplayerTestScene(bool joinRoom = true) @@ -58,9 +59,9 @@ namespace osu.Game.Tests.Visual.Multiplayer base.SetUpSteps(); if (joinRoom) - { AddUntilStep("wait for room join", () => Client.Room != null); - } } + + protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index b12bd8091d..b0c8d6d19b 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -20,6 +20,9 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// A for use in multiplayer test scenes. Should generally not be used by itself outside of a . + /// public class TestMultiplayerClient : MultiplayerClient { public override IBindable IsConnected => isConnected; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 6f4a464d57..5d66cdba02 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -11,11 +11,15 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; namespace osu.Game.Tests.Visual.Multiplayer { + /// + /// A for use in multiplayer test scenes. Should generally not be used by itself outside of a . + /// public class TestMultiplayerRoomManager : MultiplayerRoomManager { [Resolved] diff --git a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs index 67beea9117..81b93fe5b5 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs @@ -8,10 +8,14 @@ using osu.Game.Beatmaps; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { + /// + /// A very simple for use in online-play test scenes. + /// public class BasicTestRoomManager : IRoomManager { public event Action RoomsUpdated diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs index a4e0368adc..bc5d524bc4 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs @@ -8,6 +8,9 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { + /// + /// Interface that defines the dependencies required for online-play test scenes. + /// public interface IOnlinePlayTestDependencies { /// diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 39ce219092..6c1339fd85 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -14,7 +14,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// A providing all the dependencies cached by for testing s. + /// A base test scene for all online-play components and screens. /// public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestDependencies { diff --git a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs index b833a9400f..7b198c128b 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// Contains dependencies for testing online-play rooms. + /// Contains the basic dependencies of online-play test scenes. /// public class RoomTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies { From a27a647ae72a7610caa442af2c293db494ccf9ae Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 18:07:47 +0900 Subject: [PATCH 1062/1621] Rename RoomDependencies -> OnlinePlayDependencies --- ...TestSceneMultiplayerGameplayLeaderboard.cs | 4 +- .../TestSceneMultiplayerRoomManager.cs | 2 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 4 +- .../MultiplayerRoomTestDependencies.cs | 2 +- .../Multiplayer/MultiplayerTestScene.cs | 12 ++--- .../Visual/OnlinePlay/BasicTestRoomManager.cs | 2 +- .../OnlinePlay/IOnlinePlayTestDependencies.cs | 2 +- ...ncies.cs => OnlinePlayTestDependencies.cs} | 6 +-- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 50 ++++++++++--------- 9 files changed, 44 insertions(+), 40 deletions(-) rename osu.Game/Tests/Visual/OnlinePlay/{RoomTestDependencies.cs => OnlinePlayTestDependencies.cs} (92%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index b7ccc17397..2938e813b2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { private static IEnumerable users => Enumerable.Range(0, 16); - public new TestMultiplayerSpectatorClient SpectatorClient => (TestMultiplayerSpectatorClient)RoomDependencies?.SpectatorClient; + public new TestMultiplayerSpectatorClient SpectatorClient => (TestMultiplayerSpectatorClient)OnlinePlayDependencies?.SpectatorClient; private MultiplayerGameplayLeaderboard leaderboard; private OsuConfigManager config; @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised)); } - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); protected class TestDependencies : MultiplayerRoomTestDependencies { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index 225a9ff703..c750bbed4b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [HeadlessTest] public class TestSceneMultiplayerRoomManager : MultiplayerTestScene { - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); public TestSceneMultiplayerRoomManager() : base(false) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index b52b6a6a0e..fd59ebad30 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Playlists private TestRoomSettings settings; - protected override RoomTestDependencies CreateRoomDependencies() => new TestDependencies(); + protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); [SetUp] public new void Setup() => Schedule(() => @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Playlists public OsuSpriteText ErrorText => ((MatchSettings)Settings).ErrorText; } - private class TestDependencies : RoomTestDependencies + private class TestDependencies : OnlinePlayTestDependencies { protected override IRoomManager CreateRoomManager() => new TestRoomManager(); } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs index 4c5afb9b58..e7885890b4 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Contains the basic dependencies of multiplayer test scenes. /// - public class MultiplayerRoomTestDependencies : RoomTestDependencies, IMultiplayerTestDependencies + public class MultiplayerRoomTestDependencies : OnlinePlayTestDependencies, IMultiplayerTestDependencies { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 6a9ae4c772..90ffc83ccd 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -17,12 +17,12 @@ namespace osu.Game.Tests.Visual.Multiplayer public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; - public TestMultiplayerClient Client => RoomDependencies.Client; - public new TestMultiplayerRoomManager RoomManager => RoomDependencies.RoomManager; - public TestUserLookupCache LookupCache => RoomDependencies?.LookupCache; - public TestSpectatorClient SpectatorClient => RoomDependencies?.SpectatorClient; + public TestMultiplayerClient Client => OnlinePlayDependencies.Client; + public new TestMultiplayerRoomManager RoomManager => OnlinePlayDependencies.RoomManager; + public TestUserLookupCache LookupCache => OnlinePlayDependencies?.LookupCache; + public TestSpectatorClient SpectatorClient => OnlinePlayDependencies?.SpectatorClient; - protected new MultiplayerRoomTestDependencies RoomDependencies => (MultiplayerRoomTestDependencies)base.RoomDependencies; + protected new MultiplayerRoomTestDependencies OnlinePlayDependencies => (MultiplayerRoomTestDependencies)base.OnlinePlayDependencies; private readonly bool joinRoom; @@ -62,6 +62,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room join", () => Client.Room != null); } - protected override RoomTestDependencies CreateRoomDependencies() => new MultiplayerRoomTestDependencies(); + protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new MultiplayerRoomTestDependencies(); } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs index 81b93fe5b5..813e617ac5 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs @@ -14,7 +14,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// A very simple for use in online-play test scenes. + /// A very simple for use in online play test scenes. /// public class BasicTestRoomManager : IRoomManager { diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs index bc5d524bc4..8c262e718a 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs @@ -9,7 +9,7 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// Interface that defines the dependencies required for online-play test scenes. + /// Interface that defines the dependencies required for online play test scenes. /// public interface IOnlinePlayTestDependencies { diff --git a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs similarity index 92% rename from osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs rename to osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs index 7b198c128b..e45aadfd84 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/RoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs @@ -13,9 +13,9 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// Contains the basic dependencies of online-play test scenes. + /// Contains the basic dependencies of online play test scenes. /// - public class RoomTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies + public class OnlinePlayTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies { public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay private readonly List drawableComponents = new List(); private readonly DependencyContainer dependencies; - public RoomTestDependencies() + public OnlinePlayTestDependencies() { SelectedRoom = new Bindable(); RoomManager = CreateRoomManager(); diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 6c1339fd85..e1c21ce377 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -14,18 +14,22 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.OnlinePlay { /// - /// A base test scene for all online-play components and screens. + /// A base test scene for all online play components and screens. /// public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestDependencies { - public Bindable SelectedRoom => RoomDependencies?.SelectedRoom; - public IRoomManager RoomManager => RoomDependencies?.RoomManager; - public Bindable Filter => RoomDependencies?.Filter; - public OngoingOperationTracker OngoingOperationTracker => RoomDependencies?.OngoingOperationTracker; - public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => RoomDependencies?.AvailabilityTracker; + public Bindable SelectedRoom => OnlinePlayDependencies?.SelectedRoom; + public IRoomManager RoomManager => OnlinePlayDependencies?.RoomManager; + public Bindable Filter => OnlinePlayDependencies?.Filter; + public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies?.OngoingOperationTracker; + public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies?.AvailabilityTracker; - protected RoomTestDependencies RoomDependencies => delegatedDependencies?.RoomDependencies; - private DelegatedRoomDependencyContainer delegatedDependencies; + /// + /// All dependencies required for online play components and screens. + /// + protected OnlinePlayTestDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies; + + private DelegatedDependencyContainer dependencies; protected override Container Content => content; private readonly Container content; @@ -42,8 +46,8 @@ namespace osu.Game.Tests.Visual.OnlinePlay protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - delegatedDependencies = new DelegatedRoomDependencyContainer(base.CreateChildDependencies(parent)); - return delegatedDependencies; + dependencies = new DelegatedDependencyContainer(base.CreateChildDependencies(parent)); + return dependencies; } [SetUp] @@ -51,46 +55,46 @@ namespace osu.Game.Tests.Visual.OnlinePlay { // Reset the room dependencies to a fresh state. drawableDependenciesContainer.Clear(); - delegatedDependencies.RoomDependencies = CreateRoomDependencies(); - drawableDependenciesContainer.AddRange(RoomDependencies.DrawableComponents); + dependencies.OnlinePlayDependencies = CreateOnlinePlayDependencies(); + drawableDependenciesContainer.AddRange(OnlinePlayDependencies.DrawableComponents); }); /// /// Creates the room dependencies. Called every . /// /// - /// Any custom dependencies required for online-play sub-classes should be added here. + /// Any custom dependencies required for online play sub-classes should be added here. /// - protected virtual RoomTestDependencies CreateRoomDependencies() => new RoomTestDependencies(); + protected virtual OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new OnlinePlayTestDependencies(); /// - /// A providing a mutable lookup source for room dependencies. + /// A providing a mutable lookup source for online play dependencies. /// - private class DelegatedRoomDependencyContainer : IReadOnlyDependencyContainer + private class DelegatedDependencyContainer : IReadOnlyDependencyContainer { /// - /// The room's dependencies. + /// The online play dependencies. /// - public RoomTestDependencies RoomDependencies { get; set; } + public OnlinePlayTestDependencies OnlinePlayDependencies { get; set; } private readonly IReadOnlyDependencyContainer parent; private readonly DependencyContainer injectableDependencies; /// - /// Creates a new . + /// Creates a new . /// - /// The fallback to use when cannot satisfy a dependency. - public DelegatedRoomDependencyContainer(IReadOnlyDependencyContainer parent) + /// The fallback to use when cannot satisfy a dependency. + public DelegatedDependencyContainer(IReadOnlyDependencyContainer parent) { this.parent = parent; injectableDependencies = new DependencyContainer(this); } public object Get(Type type) - => RoomDependencies?.Get(type) ?? parent.Get(type); + => OnlinePlayDependencies?.Get(type) ?? parent.Get(type); public object Get(Type type, CacheInfo info) - => RoomDependencies?.Get(type, info) ?? parent.Get(type, info); + => OnlinePlayDependencies?.Get(type, info) ?? parent.Get(type, info); public void Inject(T instance) where T : class From ff5e590d323ce868cce4eeaed71280645e6ae729 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 12:00:46 +0300 Subject: [PATCH 1063/1621] Add local source for testing --- .../TestSceneRulesetSkinProvidingContainer.cs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index b6800e40e4..b058cc3694 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -19,11 +19,11 @@ namespace osu.Game.Tests.Rulesets { public class TestSceneRulesetSkinProvidingContainer : OsuTestScene { - [Resolved] - private SkinManager skins { get; set; } - private SkinRequester requester; + [Cached(typeof(ISkin))] + private readonly TestSkinProvider testSkin = new TestSkinProvider(); + protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); [Test] @@ -31,15 +31,13 @@ namespace osu.Game.Tests.Rulesets { Texture textureOnLoad = null; - AddStep("set skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info); - AddStep("setup provider", () => { var rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin); rulesetSkinProvider.Add(requester = new SkinRequester()); - requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture("hitcircle"); + requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture(TestSkinProvider.TEXTURE_NAME); Child = rulesetSkinProvider; }); @@ -69,5 +67,18 @@ namespace osu.Game.Tests.Rulesets public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); } + + private class TestSkinProvider : ISkin + { + public const string TEXTURE_NAME = "some-texture"; + + public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException(); + + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => componentName == TEXTURE_NAME ? Texture.WhitePixel : null; + + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + + public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); + } } } From 04cc390c419f73da5bc074fe0ef168f594e51a7f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 19:16:54 +0900 Subject: [PATCH 1064/1621] Fix TestSceneMultiplayer resolving the wrong client --- .../Multiplayer/TestSceneMultiplayer.cs | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 599dfb082b..c93640e7b5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -18,6 +18,7 @@ using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; @@ -30,14 +31,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayer : ScreenTestScene { - private TestMultiplayer multiplayerScreen; - private BeatmapManager beatmaps; private RulesetStore rulesets; private BeatmapSetInfo importedSet; - private TestMultiplayerClient client => multiplayerScreen.Client; - private Room room => client.APIRoom; + private DependenciesScreen dependenciesScreen; + private TestMultiplayer multiplayerScreen; + private TestMultiplayerClient client; public TestSceneMultiplayer() { @@ -229,30 +229,43 @@ namespace osu.Game.Tests.Visual.Multiplayer private void loadMultiplayer() { - AddStep("show", () => + AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer()); + + AddStep("load dependencies", () => { - multiplayerScreen = new TestMultiplayer(); + client = new TestMultiplayerClient(multiplayerScreen.RoomManager); - // Needs to be added at a higher level since the multiplayer screen becomes non-current. - Child = multiplayerScreen.Client; + // The screen gets suspended so it stops receiving updates. + Child = client; - LoadScreen(multiplayerScreen); + LoadScreen(dependenciesScreen = new DependenciesScreen(client)); }); - AddUntilStep("wait for loaded", () => multiplayerScreen.IsLoaded); + AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded); + + AddStep("load multiplayer", () => LoadScreen(multiplayerScreen)); + AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded); } - private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer + /// + /// Used for the sole purpose of adding as a resolvable dependency. + /// + private class DependenciesScreen : OsuScreen { [Cached(typeof(MultiplayerClient))] public readonly TestMultiplayerClient Client; - public TestMultiplayer() + public DependenciesScreen(TestMultiplayerClient client) { - Client = new TestMultiplayerClient((TestMultiplayerRoomManager)RoomManager); + Client = client; } + } - protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); + private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer + { + public new TestMultiplayerRoomManager RoomManager { get; private set; } + + protected override RoomManager CreateRoomManager() => RoomManager = new TestMultiplayerRoomManager(); } } } From 84c9ede966af09a0b4b1337ce7750d01b14d7c03 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 25 Jun 2021 13:17:11 +0300 Subject: [PATCH 1065/1621] Fix incorrect pushed changes This should've been in the original commit, but for some reason got deleted out. --- .../TestSceneRulesetSkinProvidingContainer.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index b058cc3694..0dde0a8194 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -21,11 +22,15 @@ namespace osu.Game.Tests.Rulesets { private SkinRequester requester; - [Cached(typeof(ISkin))] - private readonly TestSkinProvider testSkin = new TestSkinProvider(); - protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(new TestSkinProvider()); + return dependencies; + } + [Test] public void TestEarlyAddedSkinRequester() { @@ -68,7 +73,7 @@ namespace osu.Game.Tests.Rulesets public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); } - private class TestSkinProvider : ISkin + private class TestSkinProvider : ISkinSource { public const string TEXTURE_NAME = "some-texture"; @@ -79,6 +84,16 @@ namespace osu.Game.Tests.Rulesets public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); + + public event Action SourceChanged + { + add { } + remove { } + } + + public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null; + + public IEnumerable AllSources => new[] { this }; } } } From 57f2b4f812870bb1d9804ee7d8c079c106bd84d9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:09:03 +0900 Subject: [PATCH 1066/1621] Fix incorrect parent class --- .../Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index afe66d2686..4e08ffef17 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -5,11 +5,10 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; -using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerMatchFooter : OnlinePlayTestScene + public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene { [SetUp] public new void Setup() => Schedule(() => From 1ed61b9b98757b1c2b63a1127c3569f0d728624a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:11:38 +0900 Subject: [PATCH 1067/1621] Fix up dependencies class names --- .../Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs | 4 ++-- .../Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs | 4 ++-- .../Playlists/TestScenePlaylistsMatchSettingsOverlay.cs | 4 ++-- osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 4 ++-- ...tDependencies.cs => MultiplayerTestSceneDependencies.cs} | 4 ++-- osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 6 +++--- ...stDependencies.cs => OnlinePlayTestSceneDependencies.cs} | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) rename osu.Game/Tests/Visual/Multiplayer/{MultiplayerRoomTestDependencies.cs => MultiplayerTestSceneDependencies.cs} (89%) rename osu.Game/Tests/Visual/OnlinePlay/{OnlinePlayTestDependencies.cs => OnlinePlayTestSceneDependencies.cs} (94%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 2938e813b2..0e368b59dd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -98,9 +98,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised)); } - protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); + protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies(); - protected class TestDependencies : MultiplayerRoomTestDependencies + protected class TestDependencies : MultiplayerTestSceneDependencies { protected override TestSpectatorClient CreateSpectatorClient() => new TestMultiplayerSpectatorClient(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index c750bbed4b..b17427a30b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [HeadlessTest] public class TestSceneMultiplayerRoomManager : MultiplayerTestScene { - protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); + protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies(); public TestSceneMultiplayerRoomManager() : base(false) @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return room; } - private class TestDependencies : MultiplayerRoomTestDependencies + private class TestDependencies : MultiplayerTestSceneDependencies { public TestDependencies() { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index fd59ebad30..a320cb240f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Playlists private TestRoomSettings settings; - protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new TestDependencies(); + protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies(); [SetUp] public new void Setup() => Schedule(() => @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Playlists public OsuSpriteText ErrorText => ((MatchSettings)Settings).ErrorText; } - private class TestDependencies : OnlinePlayTestDependencies + private class TestDependencies : OnlinePlayTestSceneDependencies { protected override IRoomManager CreateRoomManager() => new TestRoomManager(); } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 90ffc83ccd..da01010dbf 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestUserLookupCache LookupCache => OnlinePlayDependencies?.LookupCache; public TestSpectatorClient SpectatorClient => OnlinePlayDependencies?.SpectatorClient; - protected new MultiplayerRoomTestDependencies OnlinePlayDependencies => (MultiplayerRoomTestDependencies)base.OnlinePlayDependencies; + protected new MultiplayerTestSceneDependencies OnlinePlayDependencies => (MultiplayerTestSceneDependencies)base.OnlinePlayDependencies; private readonly bool joinRoom; @@ -62,6 +62,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room join", () => Client.Room != null); } - protected override OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new MultiplayerRoomTestDependencies(); + protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new MultiplayerTestSceneDependencies(); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs similarity index 89% rename from osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs rename to osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs index e7885890b4..8fd21b5e4a 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerRoomTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs @@ -13,14 +13,14 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Contains the basic dependencies of multiplayer test scenes. /// - public class MultiplayerRoomTestDependencies : OnlinePlayTestDependencies, IMultiplayerTestDependencies + public class MultiplayerTestSceneDependencies : OnlinePlayTestSceneDependencies, IMultiplayerTestDependencies { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } public TestSpectatorClient SpectatorClient { get; } public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; - public MultiplayerRoomTestDependencies() + public MultiplayerTestSceneDependencies() { Client = new TestMultiplayerClient(RoomManager); LookupCache = new TestUserLookupCache(); diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index e1c21ce377..4920ca2058 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// All dependencies required for online play components and screens. /// - protected OnlinePlayTestDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies; + protected OnlinePlayTestSceneDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies; private DelegatedDependencyContainer dependencies; @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Any custom dependencies required for online play sub-classes should be added here. /// - protected virtual OnlinePlayTestDependencies CreateOnlinePlayDependencies() => new OnlinePlayTestDependencies(); + protected virtual OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new OnlinePlayTestSceneDependencies(); /// /// A providing a mutable lookup source for online play dependencies. @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// The online play dependencies. /// - public OnlinePlayTestDependencies OnlinePlayDependencies { get; set; } + public OnlinePlayTestSceneDependencies OnlinePlayDependencies { get; set; } private readonly IReadOnlyDependencyContainer parent; private readonly DependencyContainer injectableDependencies; diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs similarity index 94% rename from osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs rename to osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index e45aadfd84..b73a982fb6 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Contains the basic dependencies of online play test scenes. /// - public class OnlinePlayTestDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies + public class OnlinePlayTestSceneDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies { public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay private readonly List drawableComponents = new List(); private readonly DependencyContainer dependencies; - public OnlinePlayTestDependencies() + public OnlinePlayTestSceneDependencies() { SelectedRoom = new Bindable(); RoomManager = CreateRoomManager(); From c93c615f5e704d248f82fecb1f6fdda06e248aed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:15:30 +0900 Subject: [PATCH 1068/1621] Also fix up interface names --- ...TestDependencies.cs => IMultiplayerTestSceneDependencies.cs} | 2 +- osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 2 +- .../Visual/Multiplayer/MultiplayerTestSceneDependencies.cs | 2 +- ...yTestDependencies.cs => IOnlinePlayTestSceneDependencies.cs} | 2 +- osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 2 +- .../Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Tests/Visual/Multiplayer/{IMultiplayerTestDependencies.cs => IMultiplayerTestSceneDependencies.cs} (92%) rename osu.Game/Tests/Visual/OnlinePlay/{IOnlinePlayTestDependencies.cs => IOnlinePlayTestSceneDependencies.cs} (95%) diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs similarity index 92% rename from osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs rename to osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs index 46ad5a5a1c..204c189591 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Interface that defines the dependencies required for multiplayer test scenes. /// - public interface IMultiplayerTestDependencies : IOnlinePlayTestDependencies + public interface IMultiplayerTestSceneDependencies : IOnlinePlayTestSceneDependencies { /// /// The cached . diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index da01010dbf..b7d3793ab1 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// The base test scene for all multiplayer components and screens. /// - public abstract class MultiplayerTestScene : OnlinePlayTestScene, IMultiplayerTestDependencies + public abstract class MultiplayerTestScene : OnlinePlayTestScene, IMultiplayerTestSceneDependencies { public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs index 8fd21b5e4a..a2b0b066a7 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Contains the basic dependencies of multiplayer test scenes. /// - public class MultiplayerTestSceneDependencies : OnlinePlayTestSceneDependencies, IMultiplayerTestDependencies + public class MultiplayerTestSceneDependencies : OnlinePlayTestSceneDependencies, IMultiplayerTestSceneDependencies { public TestMultiplayerClient Client { get; } public TestUserLookupCache LookupCache { get; } diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs similarity index 95% rename from osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs rename to osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs index 8c262e718a..6e1e831d9b 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs @@ -11,7 +11,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Interface that defines the dependencies required for online play test scenes. /// - public interface IOnlinePlayTestDependencies + public interface IOnlinePlayTestSceneDependencies { /// /// The cached . diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 4920ca2058..997c910dd4 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// A base test scene for all online play components and screens. /// - public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestDependencies + public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestSceneDependencies { public Bindable SelectedRoom => OnlinePlayDependencies?.SelectedRoom; public IRoomManager RoomManager => OnlinePlayDependencies?.RoomManager; diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index b73a982fb6..ddbbfe501b 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// Contains the basic dependencies of online play test scenes. /// - public class OnlinePlayTestSceneDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestDependencies + public class OnlinePlayTestSceneDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestSceneDependencies { public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } From 3e5ae7ea58090d9b30d31cbb52ccb2dfd68fe45a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:44:00 +0900 Subject: [PATCH 1069/1621] Don't join room in participants test --- .../Multiplayer/TestSceneMultiplayerParticipantsList.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 7f8f04b718..e94750c695 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -22,6 +22,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerParticipantsList : MultiplayerTestScene { + public TestSceneMultiplayerParticipantsList() + : base(false) + { + } + [SetUp] public new void Setup() => Schedule(createNewParticipantsList); From d035633f9514d78b7dfc88a1f567220169eb061b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Jun 2021 20:56:13 +0900 Subject: [PATCH 1070/1621] Load participants list after joining room --- .../TestSceneMultiplayerParticipantsList.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index e94750c695..6526f7eea7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -22,14 +22,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerParticipantsList : MultiplayerTestScene { - public TestSceneMultiplayerParticipantsList() - : base(false) + [SetUpSteps] + public void SetupSteps() { + createNewParticipantsList(); } - [SetUp] - public new void Setup() => Schedule(createNewParticipantsList); - [Test] public void TestAddUser() { @@ -93,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestCorrectInitialState() { AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); - AddStep("recreate list", createNewParticipantsList); + createNewParticipantsList(); checkProgressBarVisibility(true); } @@ -238,7 +236,17 @@ namespace osu.Game.Tests.Visual.Multiplayer private void createNewParticipantsList() { - Child = new ParticipantsList { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Size = new Vector2(380, 0.7f) }; + ParticipantsList participantsList = null; + + AddStep("create new list", () => Child = participantsList = new ParticipantsList + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(380, 0.7f) + }); + + AddUntilStep("wait for list to load", () => participantsList.IsLoaded); } private void checkProgressBarVisibility(bool visible) => From 6bc71590c539ef0dba6993aab0a5a48a95dfea7e Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Fri, 25 Jun 2021 09:21:26 -0700 Subject: [PATCH 1071/1621] Disable logo click sound when exiting --- osu.Game/Screens/Menu/ButtonSystem.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index da0edd07db..38290a6530 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Menu switch (state) { default: - return true; + return false; case ButtonSystemState.Initial: State = ButtonSystemState.TopLevel; @@ -274,10 +274,6 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Play: buttonsPlay.First().Click(); return false; - - // no sound should be played if the logo is clicked on while transitioning to song select - case ButtonSystemState.EnteringMode: - return false; } } From 50c27d26357f2646548c126c7201398a23ee489e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Jun 2021 19:10:04 +0200 Subject: [PATCH 1072/1621] Update usages of `IHasTooltip` in line with framework localisation changes --- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 3 ++- .../Sliders/Components/PathControlPointPiece.cs | 3 ++- osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs | 3 ++- osu.Game/Configuration/SettingSourceAttribute.cs | 2 +- osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs | 3 ++- osu.Game/Graphics/Containers/OsuClickableContainer.cs | 3 ++- osu.Game/Graphics/Cursor/OsuTooltipContainer.cs | 3 ++- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 3 ++- osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs | 3 ++- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 3 ++- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 5 +++-- osu.Game/Overlays/BeatmapSet/BasicStats.cs | 2 +- osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 3 ++- .../Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs | 3 ++- osu.Game/Overlays/Comments/DrawableComment.cs | 5 +++-- osu.Game/Overlays/Mods/ModButton.cs | 3 ++- osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs | 3 ++- .../Overlays/Profile/Header/Components/DrawableBadge.cs | 3 ++- .../Profile/Header/Components/ExpandDetailsButton.cs | 3 ++- .../Overlays/Profile/Header/Components/FollowersButton.cs | 3 ++- osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs | 3 ++- .../Profile/Header/Components/LevelProgressBar.cs | 3 ++- .../Profile/Header/Components/MappingSubscribersButton.cs | 3 ++- .../Profile/Header/Components/MessageUserButton.cs | 3 ++- .../Profile/Header/Components/OverlinedTotalPlayTime.cs | 3 ++- .../Overlays/Profile/Header/Components/SupporterIcon.cs | 3 ++- .../Sections/Historical/DrawableMostPlayedBeatmap.cs | 2 +- osu.Game/Overlays/RestoreDefaultValueButton.cs | 3 ++- .../Overlays/Settings/Sections/Audio/OffsetSettings.cs | 3 ++- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- .../Overlays/Settings/Sections/Input/MouseSettings.cs | 3 ++- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 3 ++- .../Settings/Sections/UserInterface/GeneralSettings.cs | 3 ++- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 5 +++-- osu.Game/Overlays/Settings/SettingsButton.cs | 8 +++++--- osu.Game/Overlays/Settings/SettingsItem.cs | 4 ++-- osu.Game/Rulesets/UI/ModIcon.cs | 3 ++- .../Screens/Edit/Compose/Components/SelectionBoxButton.cs | 3 ++- .../Screens/OnlinePlay/Components/DrawableGameType.cs | 3 ++- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- osu.Game/Users/Drawables/ClickableAvatar.cs | 7 ++++--- osu.Game/Users/Drawables/DrawableFlag.cs | 3 ++- 42 files changed, 87 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 1c89d9cd00..f89750a96e 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mania.Configuration; @@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.Mania private class TimeSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString("N0") + "ms"; + public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 48e4db11ca..5b476526c9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; @@ -283,6 +284,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } - public string TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty; + public LocalisableString TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty; } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs index c5374d50ab..096bccae9e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; @@ -59,7 +60,7 @@ namespace osu.Game.Tests.Visual.UserInterface private class Icon : Container, IHasTooltip { - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public SpriteIcon SpriteIcon { get; } diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 55636495df..f373e59417 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -31,7 +31,7 @@ namespace osu.Game.Configuration { public LocalisableString Label { get; } - public string Description { get; } + public LocalisableString Description { get; } public int? OrderPosition { get; } diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs index 75c73af0ce..ce8a9c8f9f 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs @@ -4,12 +4,13 @@ using Markdig.Syntax.Inlines; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownImage : MarkdownImage, IHasTooltip { - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public OsuMarkdownImage(LinkInline linkInline) : base(linkInline.Url) diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 60ded8952d..0bc3c876e1 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.Containers @@ -24,7 +25,7 @@ namespace osu.Game.Graphics.Containers this.sampleSet = sampleSet; } - public virtual string TooltipText { get; set; } + public virtual LocalisableString TooltipText { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 57f39bb8c7..81dca99ddd 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.Cursor @@ -32,7 +33,7 @@ namespace osu.Game.Graphics.Cursor public override bool SetContent(object content) { - if (!(content is string contentString)) + if (!(content is LocalisableString contentString)) return false; if (contentString == text.Text) return true; diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 5a1eb53fe1..6ad88eaaba 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Platform; using osuTK; using osuTK.Graphics; @@ -58,6 +59,6 @@ namespace osu.Game.Graphics.UserInterface return true; } - public string TooltipText => "view in browser"; + public LocalisableString TooltipText => "view in browser"; } } diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index ac6f5ceb1b..8e82f4a7c1 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Platform; namespace osu.Game.Graphics.UserInterface @@ -105,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface private class CapsWarning : SpriteIcon, IHasTooltip { - public string TooltipText => @"caps lock is active"; + public LocalisableString TooltipText => "caps lock is active"; public CapsWarning() { diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index f58962f8e1..ae16169123 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Localisation; namespace osu.Game.Graphics.UserInterface { @@ -34,7 +35,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual string TooltipText { get; private set; } + public virtual LocalisableString TooltipText { get; private set; } /// /// Whether to format the tooltip as a percentage or the actual value. diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 795540b65d..e35d3d6461 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -13,6 +13,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; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -295,7 +296,7 @@ namespace osu.Game.Online.Leaderboards public override bool Contains(Vector2 screenSpacePos) => content.Contains(screenSpacePos); - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public ScoreComponentLabel(LeaderboardScoreStatistic statistic) { @@ -365,7 +366,7 @@ namespace osu.Game.Online.Leaderboards }; } - public string TooltipText { get; } + public LocalisableString TooltipText { get; } } public class LeaderboardScoreStatistic diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index cf74c0d4d3..b81c60a5b9 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.BeatmapSet { private readonly OsuSpriteText value; - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public LocalisableString Value { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 7ad6906cea..bb87e7151b 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -28,7 +29,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private readonly IBindable localUser = new Bindable(); - public string TooltipText + public LocalisableString TooltipText { get { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs index 6d27342049..cef623e59b 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -26,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private readonly bool noVideo; - public string TooltipText => button.Enabled.Value ? "download this beatmap" : "login to download"; + public LocalisableString TooltipText => button.Enabled.Value ? "download this beatmap" : "login to download"; private readonly IBindable localUser = new Bindable(); diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 7c47ac655f..d94f8c4b8b 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -20,6 +20,7 @@ using System; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.IEnumerableExtensions; using System.Collections.Specialized; +using osu.Framework.Localisation; using osu.Game.Overlays.Comments.Buttons; namespace osu.Game.Overlays.Comments @@ -395,7 +396,7 @@ namespace osu.Game.Overlays.Comments private class ParentUsername : FillFlowContainer, IHasTooltip { - public string TooltipText => getParentMessage(); + public LocalisableString TooltipText => getParentMessage(); private readonly Comment parentComment; @@ -427,7 +428,7 @@ namespace osu.Game.Overlays.Comments if (parentComment == null) return string.Empty; - return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? @"deleted" : string.Empty; + return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? "deleted" : string.Empty; } } } diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 70424101fd..d0bd24496a 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -14,6 +14,7 @@ using System; using System.Linq; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -34,7 +35,7 @@ namespace osu.Game.Overlays.Mods /// public Action SelectionChanged; - public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; + public LocalisableString TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; private const Easing mod_switch_easing = Easing.InOutSine; private const double mod_switch_duration = 120; diff --git a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs index 87b9d89d4d..0ece96b56c 100644 --- a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs +++ b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics.UserInterface; using osu.Framework.Allocation; using osuTK.Graphics; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; namespace osu.Game.Overlays { @@ -56,7 +57,7 @@ namespace osu.Game.Overlays [Resolved] private OverlayColourProvider colourProvider { get; set; } - public string TooltipText => $@"{Value} view"; + public LocalisableString TooltipText => $@"{Value} view"; private readonly SpriteIcon icon; diff --git a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs index 7eed4d3b6b..74f3ed846b 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; using osu.Game.Users; using osuTK; @@ -42,6 +43,6 @@ namespace osu.Game.Overlays.Profile.Header.Components InternalChild.FadeInFromZero(200); } - public string TooltipText => badge.Description; + public LocalisableString TooltipText => badge.Description; } } diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs index 29e13e4f51..527c70685f 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osuTK; namespace osu.Game.Overlays.Profile.Header.Components @@ -14,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly BindableBool DetailsVisible = new BindableBool(); - public override string TooltipText => DetailsVisible.Value ? "collapse" : "expand"; + public override LocalisableString TooltipText => DetailsVisible.Value ? "collapse" : "expand"; private SpriteIcon icon; diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index bd8aa7b3bd..db94762efd 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -12,7 +13,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override string TooltipText => "followers"; + public override LocalisableString TooltipText => "followers"; protected override IconUsage Icon => FontAwesome.Solid.User; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs index 29471375b5..a0b8ef0f11 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public string TooltipText { get; } + public LocalisableString TooltipText { get; } private OsuSpriteText levelText; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs index c97df3bc4d..528b05a80a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public string TooltipText { get; } + public LocalisableString TooltipText { get; } private Bar levelProgressBar; private OsuSpriteText levelProgressText; diff --git a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs index b4d7c9a05c..ae3d024fbf 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -12,7 +13,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override string TooltipText => "mapping subscribers"; + public override LocalisableString TooltipText => "mapping subscribers"; protected override IconUsage Icon => FontAwesome.Solid.Bell; diff --git a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs index 228765ee1a..4c2cc768ce 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Users; @@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override string TooltipText => "send message"; + public override LocalisableString TooltipText => "send message"; [Resolved(CanBeNull = true)] private ChannelManager channelManager { get; set; } diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs index be96840217..aa7cb8636a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -14,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public string TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } private OverlinedInfoContainer info; diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index d581e2750c..9a43997030 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics; namespace osu.Game.Overlays.Profile.Header.Components @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private readonly FillFlowContainer iconContainer; private readonly CircularContainer content; - public string TooltipText => "osu!supporter"; + public LocalisableString TooltipText => "osu!supporter"; public int SupportLevel { diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 6d6ff32aac..6f1869966a 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private class PlayCountText : CompositeDrawable, IHasTooltip { - public string TooltipText => "times played"; + public LocalisableString TooltipText => "times played"; public PlayCountText(int playCount) { diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 213ad2ba68..fe36f6ba6d 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -76,7 +77,7 @@ namespace osu.Game.Overlays UpdateState(); } - public string TooltipText => "revert to default"; + public LocalisableString TooltipText => "revert to default"; protected override bool OnHover(HoverEvent e) { diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index c9a81b955b..1ae297f2a9 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -32,7 +33,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private class OffsetSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString(@"0ms"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0ms"); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 937bcc8abf..669753d2cb 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -233,7 +233,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private class UIScaleSlider : OsuSliderBar { - public override string TooltipText => base.TooltipText + "x"; + public override LocalisableString TooltipText => base.TooltipText + "x"; } private class ResolutionSettingsDropdown : SettingsDropdown diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index fb908a7669..e87572e2ca 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Mouse; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Input; @@ -116,7 +117,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private class SensitivitySlider : OsuSliderBar { - public override string TooltipText => Current.Disabled ? "enable high precision mouse to adjust sensitivity" : $"{base.TooltipText}x"; + public override LocalisableString TooltipText => Current.Disabled ? "enable high precision mouse to adjust sensitivity" : $"{base.TooltipText}x"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index 101d8f43f7..8aeb440be1 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.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 osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections @@ -10,6 +11,6 @@ namespace osu.Game.Overlays.Settings.Sections /// internal class SizeSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString(@"0.##x"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x"); } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index 19adfc5dd9..a6eb008623 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -44,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class TimeSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString("N0") + "ms"; + public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index c73a783d37..2470c0a6c5 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -62,12 +63,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class MaximumStarsSlider : StarsSlider { - public override string TooltipText => Current.IsDefault ? "no limit" : base.TooltipText; + public override LocalisableString TooltipText => Current.IsDefault ? "no limit" : base.TooltipText; } private class StarsSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString(@"0.## stars"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); } } } diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index 088d69c031..87b1aa0e46 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings @@ -17,14 +18,15 @@ namespace osu.Game.Overlays.Settings Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }; } - public string TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } public override IEnumerable FilterTerms { get { - if (TooltipText != null) - return base.FilterTerms.Append(TooltipText); + if (TooltipText != default) + // TODO: this won't work as intended once the tooltip text is translated. + return base.FilterTerms.Append(TooltipText.ToString()); return base.FilterTerms; } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 807916e7f6..15a0a42d31 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Settings public bool ShowsDefaultIndicator = true; - public string TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } [Resolved] private OsuColour colours { get; set; } @@ -142,4 +142,4 @@ namespace osu.Game.Overlays.Settings labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1; } } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index cae5da3d16..725cfa9c26 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osuTK; using osu.Framework.Bindables; +using osu.Framework.Localisation; namespace osu.Game.Rulesets.UI { @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.UI private const float size = 80; - public virtual string TooltipText => showTooltip ? mod.IconTooltip : null; + public virtual LocalisableString TooltipText => showTooltip ? mod.IconTooltip : null; private Mod mod; private readonly bool showTooltip; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index 3b1dae6c3d..3ac40fda0f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osuTK; using osuTK.Graphics; @@ -58,6 +59,6 @@ namespace osu.Game.Screens.Edit.Compose.Components icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); } - public string TooltipText { get; } + public LocalisableString TooltipText { get; } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs b/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs index c4dc2a2b8f..ae1ca1b967 100644 --- a/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs +++ b/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Online.Rooms; @@ -16,7 +17,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { private readonly GameType type; - public string TooltipText => type.Name; + public LocalisableString TooltipText => type.Name; public DrawableGameType(GameType type) { diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index e1cf0cef4e..4a35202df2 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -431,7 +431,7 @@ namespace osu.Game.Screens.Select public class InfoLabel : Container, IHasTooltip { - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public InfoLabel(BeatmapStatistic statistic) { diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index c3bf740108..f73489ac61 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Containers; namespace osu.Game.Users.Drawables @@ -68,11 +69,11 @@ namespace osu.Game.Users.Drawables private class ClickableArea : OsuClickableContainer { - private string tooltip = default_tooltip_text; + private LocalisableString tooltip = default_tooltip_text; - public override string TooltipText + public override LocalisableString TooltipText { - get => Enabled.Value ? tooltip : null; + get => Enabled.Value ? tooltip : default; set => tooltip = value; } diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 1d648e46b6..aea40a01ae 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; namespace osu.Game.Users.Drawables { @@ -13,7 +14,7 @@ namespace osu.Game.Users.Drawables { private readonly Country country; - public string TooltipText => country?.FullName; + public LocalisableString TooltipText => country?.FullName; public DrawableFlag(Country country) { From 3b822cd5cf45af3cd21bbfed9471b4f3b60a07c5 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 26 Jun 2021 11:19:14 +0800 Subject: [PATCH 1073/1621] Refactor `SeedSettingsControl` and related controls --- .../Overlays/Settings/OutlinedNumberBox.cs | 10 ++++ osu.Game/Overlays/Settings/OutlinedTextBox.cs | 49 +++++++++++++++++++ .../Overlays/Settings/SettingsNumberBox.cs | 8 +-- osu.Game/Overlays/Settings/SettingsTextBox.cs | 47 +----------------- osu.Game/Rulesets/Mods/SeedSettingsControl.cs | 33 +++---------- 5 files changed, 70 insertions(+), 77 deletions(-) create mode 100644 osu.Game/Overlays/Settings/OutlinedNumberBox.cs create mode 100644 osu.Game/Overlays/Settings/OutlinedTextBox.cs diff --git a/osu.Game/Overlays/Settings/OutlinedNumberBox.cs b/osu.Game/Overlays/Settings/OutlinedNumberBox.cs new file mode 100644 index 0000000000..6fcadc09e1 --- /dev/null +++ b/osu.Game/Overlays/Settings/OutlinedNumberBox.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.Overlays.Settings +{ + public class OutlinedNumberBox : OutlinedTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } +} diff --git a/osu.Game/Overlays/Settings/OutlinedTextBox.cs b/osu.Game/Overlays/Settings/OutlinedTextBox.cs new file mode 100644 index 0000000000..93eaf74b77 --- /dev/null +++ b/osu.Game/Overlays/Settings/OutlinedTextBox.cs @@ -0,0 +1,49 @@ +// 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 osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Settings +{ + public class OutlinedTextBox : 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/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index ca9a8e9c08..d4d1fc8610 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -7,15 +7,11 @@ namespace osu.Game.Overlays.Settings { public class SettingsNumberBox : SettingsItem { - protected override Drawable CreateControl() => new NumberBox + protected override Drawable CreateControl() => new OutlinedNumberBox { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true }; - - 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 25424e85a1..d28dbf1068 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -1,60 +1,17 @@ // 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 { public class SettingsTextBox : SettingsItem { - protected override Drawable CreateControl() => new TextBox + protected override Drawable CreateControl() => new OutlinedTextBox { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true, + CommitOnFocusLost = true }; - - public class TextBox : 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/Rulesets/Mods/SeedSettingsControl.cs b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs index 1280197532..1eaf31874b 100644 --- a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods } } - private readonly SettingsNumberBox.NumberBox seedNumberBox; + private readonly OutlinedNumberBox seedNumberBox; public SeedControl() { @@ -42,31 +42,11 @@ namespace osu.Game.Rulesets.Mods InternalChildren = new[] { - new GridContainer + seedNumberBox = new OutlinedNumberBox { + Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 2), - new Dimension(GridSizeMode.Relative, 0.25f) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - seedNumberBox = new SettingsNumberBox.NumberBox - { - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true - } - } - } + CommitOnFocusLost = true } }; @@ -83,8 +63,9 @@ namespace osu.Game.Rulesets.Mods protected override void Update() { - if (current.Value == null) - seedNumberBox.Text = current.Current.Value.ToString(); + base.Update(); + if (Current.Value == null) + seedNumberBox.Current.Value = ""; } } } From 3eaa04115f3dc6240e0b7231103db9c875168294 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 26 Jun 2021 11:34:10 +0800 Subject: [PATCH 1074/1621] Use `OsuHitObjectGenerationUtils` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 78 +--------------------- 1 file changed, 2 insertions(+), 76 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index bd3287634c..1a6de87745 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -25,6 +25,7 @@ using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.Utils; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Skinning; @@ -328,7 +329,7 @@ namespace osu.Game.Rulesets.Osu.Mods distance * MathF.Sin(direction) ); // Rotate the new circle away from playfield border - relativePos = getRotatedVector(lastPos, relativePos); + relativePos = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastPos, relativePos, edge_rotation_multiplier); direction = MathF.Atan2(relativePos.Y, relativePos.X); var newPosition = Vector2.Add(lastPos, relativePos); @@ -486,81 +487,6 @@ namespace osu.Game.Rulesets.Osu.Mods return objectsToCheck.Any(h => Vector2.Distance(h.Position, target.Position) < target.Radius * 2); } - /// - /// Determines the position of the current hit object relative to the previous one. - /// - /// The position of the current hit object relative to the previous one - private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev) - { - var relativeRotationDistance = 0f; - var playfieldMiddle = Vector2.Divide(OsuPlayfield.BASE_SIZE, 2); - - if (prevPosChanged.X < playfieldMiddle.X) - { - relativeRotationDistance = Math.Max( - (border_distance_x - prevPosChanged.X) / border_distance_x, - relativeRotationDistance - ); - } - else - { - relativeRotationDistance = Math.Max( - (prevPosChanged.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x, - relativeRotationDistance - ); - } - - if (prevPosChanged.Y < playfieldMiddle.Y) - { - relativeRotationDistance = Math.Max( - (border_distance_y - prevPosChanged.Y) / border_distance_y, - relativeRotationDistance - ); - } - else - { - relativeRotationDistance = Math.Max( - (prevPosChanged.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y, - relativeRotationDistance - ); - } - - return rotateVectorTowardsVector( - posRelativeToPrev, - Vector2.Subtract(playfieldMiddle, prevPosChanged), - Math.Min(1, relativeRotationDistance * edge_rotation_multiplier) - ); - } - - /// - /// Rotates vector "initial" towards vector "destination" - /// - /// Vector to rotate to "destination" - /// Vector "initial" should be rotated to - /// - /// The angle the vector should be rotated relative to the difference between the angles of - /// the the two vectors. - /// - /// Resulting vector - private Vector2 rotateVectorTowardsVector(Vector2 initial, Vector2 destination, float relativeDistance) - { - var initialAngleRad = Math.Atan2(initial.Y, initial.X); - var destAngleRad = Math.Atan2(destination.Y, destination.X); - - var diff = destAngleRad - initialAngleRad; - - while (diff < -Math.PI) diff += 2 * Math.PI; - - while (diff > Math.PI) diff -= 2 * Math.PI; - - var finalAngleRad = initialAngleRad + relativeDistance * diff; - - return new Vector2( - initial.Length * (float)Math.Cos(finalAngleRad), - initial.Length * (float)Math.Sin(finalAngleRad) - ); - } - /// /// Move the hit object into playfield, taking its radius into account. /// From e5eea503dbdfebee5f2911354ded244885a5f5d1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 26 Jun 2021 12:21:49 +0300 Subject: [PATCH 1075/1621] Remove finalizer logic from `ResourcesSkin` --- osu.Game/Skinning/ResourcesSkin.cs | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/osu.Game/Skinning/ResourcesSkin.cs b/osu.Game/Skinning/ResourcesSkin.cs index 3d17d7cc3d..90020495c3 100644 --- a/osu.Game/Skinning/ResourcesSkin.cs +++ b/osu.Game/Skinning/ResourcesSkin.cs @@ -19,7 +19,7 @@ namespace osu.Game.Skinning /// /// An that uses an underlying with namespaces for resources retrieval. /// - public class ResourcesSkin : ISkin + public class ResourcesSkin : ISkin, IDisposable { private readonly TextureStore textures; private readonly ISampleStore samples; @@ -48,33 +48,10 @@ namespace osu.Game.Skinning public IBindable? GetConfig(TLookup lookup) => null; - #region Disposal - - ~ResourcesSkin() - { - // required to potentially clean up sample store from audio hierarchy. - Dispose(false); - } - public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool isDisposed; - - protected virtual void Dispose(bool isDisposing) - { - if (isDisposed) - return; - - isDisposed = true; - textures.Dispose(); samples.Dispose(); } - - #endregion } } From e8e9fdd5331dec8d6522c23dcb7b03827b4ef666 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 26 Jun 2021 12:23:05 +0300 Subject: [PATCH 1076/1621] Dispose `ResourcesSkin` before clearing skin sources --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 54bf91523f..8a807eff21 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -72,6 +73,9 @@ namespace osu.Game.Skinning protected virtual void UpdateSkins() { + foreach (var resourcesSkin in SkinSources.OfType()) + resourcesSkin.Dispose(); + SkinSources.Clear(); foreach (var skin in skinSource.AllSources) From 8e1bcc4d6bd751a5112451f3f9c0ed06a8937714 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 26 Jun 2021 21:02:31 +0700 Subject: [PATCH 1077/1621] add overall difficulty in filter criteria --- osu.Game/Screens/Select/FilterCriteria.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 208048380a..b9e912df8e 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -24,6 +24,7 @@ namespace osu.Game.Screens.Select public OptionalRange ApproachRate; public OptionalRange DrainRate; public OptionalRange CircleSize; + public OptionalRange OverallDifficulty; public OptionalRange Length; public OptionalRange BPM; public OptionalRange BeatDivisor; From 4df4afe533aed92ee7c8d7f22d14b1047007283b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 26 Jun 2021 21:02:57 +0700 Subject: [PATCH 1078/1621] add test for overall difficulty filter query --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 9bd262a569..a55bdd2df8 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -90,6 +90,20 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.Less(filterCriteria.DrainRate.Min, 6.1f); } + [Test] + public void TestApplyOverallDifficultyQueries() + { + const string query = "od>4 easy od<8"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("easy", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.Greater(filterCriteria.OverallDifficulty.Min, 4.0); + Assert.Less(filterCriteria.OverallDifficulty.Min, 4.1); + Assert.Greater(filterCriteria.OverallDifficulty.Max, 7.9); + Assert.Less(filterCriteria.OverallDifficulty.Max, 8.0); + } + [Test] public void TestApplyBPMQueries() { From 2b1d3c8e9c9ce4eb5e5790032d64da556a0f7c6f Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 26 Jun 2021 21:05:01 +0700 Subject: [PATCH 1079/1621] add od filter in search filter --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 1 + osu.Game/Screens/Select/FilterQueryParser.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 521b90202d..f95ddfee41 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -42,6 +42,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate); match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate); match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize); + match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(Beatmap.BaseDifficulty.OverallDifficulty); match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(Beatmap.Length); match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(Beatmap.BPM); diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index db2803d29a..72d10019b2 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -51,6 +51,9 @@ namespace osu.Game.Screens.Select case "cs": return TryUpdateCriteriaRange(ref criteria.CircleSize, op, value); + case "od": + return TryUpdateCriteriaRange(ref criteria.OverallDifficulty, op, value); + case "bpm": return TryUpdateCriteriaRange(ref criteria.BPM, op, value, 0.01d / 2); From d8117fa73032af9b130c768fd1b1c0a694268580 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:20:34 +0200 Subject: [PATCH 1080/1621] Add muted objects check --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 1 + .../Rulesets/Edit/Checks/CheckMutedObjects.cs | 133 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index d208c7fe07..462a87af85 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Edit // Audio new CheckAudioPresence(), new CheckAudioQuality(), + new CheckMutedObjects(), // Compose new CheckUnsnappedObjects(), diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs new file mode 100644 index 0000000000..cbe7c7fbab --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -0,0 +1,133 @@ +// 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.Utils; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckMutedObjects : ICheck + { + /// + /// Volume percentages lower than this are typically inaudible. + /// + private const int muted_threshold = 5; + + /// + /// Volume percentages lower than this can sometimes be inaudible depending on sample used and music volume. + /// + private const int low_volume_threshold = 20; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Low volume hitobjects"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateMutedActive(this), + new IssueTemplateLowVolumeActive(this), + new IssueTemplateMutedPassive(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + foreach (var hitObject in context.Beatmap.HitObjects) + { + // Worth keeping in mind: The samples of an object always play at its end time. + // Objects like spinners have no sound at its start because of this, while hold notes have nested objects to accomplish this. + foreach (var nestedHitObject in hitObject.NestedHitObjects) + { + foreach (var issue in getVolumeIssues(hitObject, nestedHitObject)) + yield return issue; + } + + foreach (var issue in getVolumeIssues(hitObject)) + yield return issue; + } + } + + private IEnumerable getVolumeIssues(HitObject hitObject, HitObject sampledHitObject = null) + { + sampledHitObject ??= hitObject; + if (!sampledHitObject.Samples.Any()) + yield break; + + // Samples that allow themselves to be overridden by control points have a volume of 0. + int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume > 0 ? sample.Volume : sampledHitObject.SampleControlPoint.SampleVolume); + double samplePlayTime = sampledHitObject.GetEndTime(); + + bool head = Precision.AlmostEquals(samplePlayTime, hitObject.StartTime, 1f); + bool tail = Precision.AlmostEquals(samplePlayTime, hitObject.GetEndTime(), 1f); + bool repeat = false; + + if (hitObject is IHasRepeats hasRepeats && !head && !tail) + { + double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); + repeat = Precision.AlmostEquals((samplePlayTime - hitObject.StartTime) % spanDuration, 0f, 1f); + } + + // We only care about samples played on the edges of objects, not ones like spinnerspin or slidertick. + if (!head && !tail && !repeat) + yield break; + + string postfix = null; + if (hitObject is IHasDuration) + postfix = head ? "head" : tail ? "tail" : "repeat"; + + if (maxVolume <= muted_threshold) + { + if (head) + yield return new IssueTemplateMutedActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); + else + yield return new IssueTemplateMutedPassive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); + } + else if (maxVolume <= low_volume_threshold && head) + { + yield return new IssueTemplateLowVolumeActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); + } + } + + public abstract class IssueTemplateMuted : IssueTemplate + { + protected IssueTemplateMuted(ICheck check, IssueType type, string unformattedMessage) + : base(check, type, unformattedMessage) + { + } + + public Issue Create(HitObject hitobject, double volume, double time, string postfix = "") + { + string objectName = hitobject.GetType().Name; + if (!string.IsNullOrEmpty(postfix)) + objectName += " " + postfix; + + return new Issue(hitobject, this, objectName, volume) { Time = time }; + } + } + + public class IssueTemplateMutedActive : IssueTemplateMuted + { + public IssueTemplateMutedActive(ICheck check) + : base(check, IssueType.Problem, "{0} has a volume of {1:0%}. Clickable objects must have clearly audible feedback.") + { + } + } + + public class IssueTemplateLowVolumeActive : IssueTemplateMuted + { + public IssueTemplateLowVolumeActive(ICheck check) + : base(check, IssueType.Warning, "{0} has a volume of {1:0%}, ensure this is audible.") + { + } + } + + public class IssueTemplateMutedPassive : IssueTemplateMuted + { + public IssueTemplateMutedPassive(ICheck check) + : base(check, IssueType.Negligible, "{0} has a volume of {1:0%}, ensure there is no distinct sound here in the song if inaudible.") + { + } + } + } +} From 4b436b774dcb3c35d145410fb56edc12e2c66ebb Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:20:46 +0200 Subject: [PATCH 1081/1621] Add few hitsounds check --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 1 + .../Rulesets/Edit/Checks/CheckFewHitsounds.cs | 161 ++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 462a87af85..706eec226c 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Edit new CheckAudioPresence(), new CheckAudioQuality(), new CheckMutedObjects(), + new CheckFewHitsounds(), // Compose new CheckUnsnappedObjects(), diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs new file mode 100644 index 0000000000..07ca470e62 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -0,0 +1,161 @@ +// 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.Game.Audio; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckFewHitsounds : ICheck + { + /// + /// 2 measures (4/4) of 120 BPM, typically makes up a few patterns in the map. + /// This is almost always ok, but can still be useful for the mapper to make sure hitsounding coverage is good. + /// + private const int negligible_threshold_time = 4000; + + /// + /// 4 measures (4/4) of 120 BPM, typically makes up a large portion of a section in the song. + /// This is ok if the section is a quiet intro, for example. + /// + private const int warning_threshold_time = 8000; + + /// + /// 12 measures (4/4) of 120 BPM, typically makes up multiple sections in the song. + /// + private const int problem_threshold_time = 24000; + + // Should pass at least this many objects without hitsounds to be considered an issue (should work for Easy diffs too). + private const int warning_threshold_objects = 4; + private const int problem_threshold_objects = 16; + + private static readonly string[] hitsound_types = { HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_FINISH }; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Few or no hitsounds"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateLongPeriodProblem(this), + new IssueTemplateLongPeriodWarning(this), + new IssueTemplateNoHitsounds(this) + }; + + private bool hasHitsounds; + private int objectsWithoutHitsounds; + private double lastHitsoundTime; + + public IEnumerable Run(BeatmapVerifierContext context) + { + if (!context.Beatmap.HitObjects.Any()) + yield break; + + hasHitsounds = false; + objectsWithoutHitsounds = 0; + lastHitsoundTime = context.Beatmap.HitObjects.First().StartTime; + + var hitObjectCount = context.Beatmap.HitObjects.Count; + + for (int i = 0; i < hitObjectCount; ++i) + { + var hitObject = context.Beatmap.HitObjects[i]; + + // Samples play on the end of objects. Some objects have nested objects to accomplish playing them elsewhere (e.g. slider head/repeat). + foreach (var nestedHitObject in hitObject.NestedHitObjects) + { + foreach (var issue in applyHitsoundUpdate(nestedHitObject)) + yield return issue; + } + + // This is used to perform an update at the end so that the period after the last hitsounded object can be an issue. + bool isLastObject = i == hitObjectCount - 1; + + foreach (var issue in applyHitsoundUpdate(hitObject, isLastObject)) + yield return issue; + } + + if (!hasHitsounds) + yield return new IssueTemplateNoHitsounds(this).Create(); + } + + private IEnumerable applyHitsoundUpdate(HitObject hitObject, bool isLastObject = false) + { + var time = hitObject.GetEndTime(); + + // Only generating issues on hitsounded or last objects ensures we get one issue per long period. + // If there are no hitsounds we let the "No hitsounds" template take precedence. + if (hasHitsound(hitObject) || isLastObject && hasHitsounds) + { + var timeWithoutHitsounds = time - lastHitsoundTime; + + if (timeWithoutHitsounds > problem_threshold_time && objectsWithoutHitsounds > problem_threshold_objects) + yield return new IssueTemplateLongPeriodProblem(this).Create(lastHitsoundTime, timeWithoutHitsounds); + else if (timeWithoutHitsounds > warning_threshold_time && objectsWithoutHitsounds > warning_threshold_objects) + yield return new IssueTemplateLongPeriodWarning(this).Create(lastHitsoundTime, timeWithoutHitsounds); + else if (timeWithoutHitsounds > negligible_threshold_time && objectsWithoutHitsounds > warning_threshold_objects) + yield return new IssueTemplateLongPeriodNegligible(this).Create(lastHitsoundTime, timeWithoutHitsounds); + } + + if (hasHitsound(hitObject)) + { + hasHitsounds = true; + objectsWithoutHitsounds = 0; + lastHitsoundTime = time; + } + else if (couldHaveHitsound(hitObject)) + ++objectsWithoutHitsounds; + } + + private bool hasHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitsound); + private bool couldHaveHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitnormal); + + private bool isHitsound(HitSampleInfo sample) => hitsound_types.Any(sample.Name.Contains); + private bool isHitnormal(HitSampleInfo sample) => sample.Name.Contains(HitSampleInfo.HIT_NORMAL); + + public abstract class IssueTemplateLongPeriod : IssueTemplate + { + protected IssueTemplateLongPeriod(ICheck check, IssueType type) + : base(check, type, "Long period without hitsounds ({0:F1} seconds).") + { + } + + public Issue Create(double time, double duration) => new Issue(this, duration / 1000f) { Time = time }; + } + + public class IssueTemplateLongPeriodProblem : IssueTemplateLongPeriod + { + public IssueTemplateLongPeriodProblem(ICheck check) + : base(check, IssueType.Problem) + { + } + } + + public class IssueTemplateLongPeriodWarning : IssueTemplateLongPeriod + { + public IssueTemplateLongPeriodWarning(ICheck check) + : base(check, IssueType.Warning) + { + } + } + + public class IssueTemplateLongPeriodNegligible : IssueTemplateLongPeriod + { + public IssueTemplateLongPeriodNegligible(ICheck check) + : base(check, IssueType.Negligible) + { + } + } + + public class IssueTemplateNoHitsounds : IssueTemplate + { + public IssueTemplateNoHitsounds(ICheck check) + : base(check, IssueType.Problem, "There are no hitsounds.") + { + } + + public Issue Create() => new Issue(this); + } + } +} From 7b9569a1176d7a982ef6e9212574a9385c9d665b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:21:01 +0200 Subject: [PATCH 1082/1621] Add muted object check tests --- .../Editing/Checks/CheckMutedObjectsTest.cs | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs new file mode 100644 index 0000000000..4bfe62a64d --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs @@ -0,0 +1,317 @@ +// 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 System.Threading; +using NUnit.Framework; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckMutedObjectsTest + { + private CheckMutedObjects check; + private ControlPointInfo cpi; + + private const int volume_regular = 50; + private const int volume_low = 15; + private const int volume_muted = 5; + + private sealed class MockNestableHitObject : HitObject, IHasDuration + { + private readonly IEnumerable toBeNested; + + public MockNestableHitObject(IEnumerable toBeNested, double startTime, double endTime) + { + this.toBeNested = toBeNested; + StartTime = startTime; + EndTime = endTime; + } + + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) + { + foreach (var hitObject in toBeNested) + AddNested(hitObject); + } + + public double EndTime { get; } + + public double Duration + { + get => EndTime - StartTime; + set => throw new System.NotImplementedException(); + } + } + + [SetUp] + public void Setup() + { + check = new CheckMutedObjects(); + + cpi = new ControlPointInfo(); + cpi.Add(0, new SampleControlPoint { SampleVolume = volume_regular }); + cpi.Add(1000, new SampleControlPoint { SampleVolume = volume_low }); + cpi.Add(2000, new SampleControlPoint { SampleVolume = volume_muted }); + } + + [Test] + public void TestNormalControlPointVolume() + { + var hitcircle = new HitCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertOk(new List { hitcircle }); + } + + [Test] + public void TestLowControlPointVolume() + { + var hitcircle = new HitCircle + { + StartTime = 1000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertLowVolume(new List { hitcircle }); + } + + [Test] + public void TestMutedControlPointVolume() + { + var hitcircle = new HitCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { hitcircle }); + } + + [Test] + public void TestNormalSampleVolume() + { + // The sample volume should take precedence over the control point volume. + var hitcircle = new HitCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertOk(new List { hitcircle }); + } + + [Test] + public void TestLowSampleVolume() + { + var hitcircle = new HitCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_low) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertLowVolume(new List { hitcircle }); + } + + [Test] + public void TestMutedSampleVolume() + { + var hitcircle = new HitCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { hitcircle }); + } + + [Test] + public void TestNormalSampleVolumeSlider() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick", volume: volume_muted) } // Should be fine. + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertOk(new List { slider }); + } + + [Test] + public void TestMutedSampleVolumeSliderHead() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } // Applies to the tail. + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { slider }); + } + + [Test] + public void TestMutedSampleVolumeSliderTail() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 2500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } // Applies to the tail. + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMutedPassive(new List { slider }); + } + + [Test] + public void TestMutedControlPointVolumeSliderHead() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 2250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 2000, endTime: 2500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { slider }); + } + + [Test] + public void TestMutedControlPointVolumeSliderTail() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + // Ends after the 5% control point. + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 2500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMutedPassive(new List { slider }); + } + + private void assertOk(List hitObjects) + { + Assert.That(check.Run(getContext(hitObjects)), Is.Empty); + } + + private void assertLowVolume(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckMutedObjects.IssueTemplateLowVolumeActive)); + } + + private void assertMuted(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckMutedObjects.IssueTemplateMutedActive)); + } + + private void assertMutedPassive(List hitObjects) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Any(issue => issue.Template is CheckMutedObjects.IssueTemplateMutedPassive)); + } + + private BeatmapVerifierContext getContext(List hitObjects) + { + var beatmap = new Beatmap + { + ControlPointInfo = cpi, + HitObjects = hitObjects + }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} From a5abc664f30b25c249cb39e50ee67067eccebfb7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:21:15 +0200 Subject: [PATCH 1083/1621] Add few hitsounds check tests --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs new file mode 100644 index 0000000000..8ae8cd8163 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -0,0 +1,166 @@ +// 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.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckFewHitsoundsTest + { + private CheckFewHitsounds check; + + [SetUp] + public void Setup() + { + check = new CheckFewHitsounds(); + } + + [Test] + public void TestHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 16; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if ((i + 1) % 2 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP)); + if ((i + 1) % 3 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + if ((i + 1) % 4 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + assertOk(hitObjects); + } + + [Test] + public void TestLightlyHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 30; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if (i % 8 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + assertLongPeriodNegligible(hitObjects, count: 3); + } + + [Test] + public void TestRarelyHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 30; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if (i == 0 || i == 15) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + // Should prompt one warning between 1st and 11th, and another between 11th and 20th. + assertLongPeriodWarning(hitObjects, count: 2); + } + + [Test] + public void TestExtremelyRarelyHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 80; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if (i == 40) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + // Should prompt one problem between 1st and 40th, and another between 40th and 80th. + assertLongPeriodProblem(hitObjects, count: 2); + } + + [Test] + public void TestNotHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 20; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + // Should prompt one problem between 1st and 40th, and another between 40th and 80th. + assertNoHitsounds(hitObjects); + } + + private void assertOk(List hitObjects) + { + Assert.That(check.Run(getContext(hitObjects)), Is.Empty); + } + + private void assertLongPeriodProblem(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodProblem)); + } + + private void assertLongPeriodWarning(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodWarning)); + } + + private void assertLongPeriodNegligible(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodNegligible)); + } + + private void assertNoHitsounds(List hitObjects) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Any(issue => issue.Template is CheckFewHitsounds.IssueTemplateNoHitsounds)); + } + + private BeatmapVerifierContext getContext(List hitObjects) + { + var beatmap = new Beatmap { HitObjects = hitObjects }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} From 82b64f5589a950b43afc9dc9d6b0e02c548e285c Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:57:12 +0200 Subject: [PATCH 1084/1621] Add hitsounded with break test --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 8ae8cd8163..8edc6d096e 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -47,6 +47,31 @@ namespace osu.Game.Tests.Editing.Checks assertOk(hitObjects); } + [Test] + public void TestHitsoundedWithBreak() + { + var hitObjects = new List(); + + for (int i = 0; i < 32; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if ((i + 1) % 2 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP)); + if ((i + 1) % 3 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + if ((i + 1) % 4 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); + // Leaves a gap in which no hitsounds exist or can be added, and so shouldn't be an issue. + if (i > 8 && i < 24) + continue; + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + assertOk(hitObjects); + } + [Test] public void TestLightlyHitsounded() { From 51888d0d5a3fbbcf8fbd4787ffa58b9f3d7b56e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 19:27:34 +0200 Subject: [PATCH 1085/1621] Rename test methods --- .../Visual/Online/TestSceneBeatmapListingOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index cd382c2bb2..16122e496f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestNonSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() + public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithoutResults() { AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() + public void TestUserWithSupporterUsesSupporterOnlyFiltersWithoutResults() { AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestNonSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() + public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithResults() { AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); @@ -162,7 +162,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() + public void TestUserWithSupporterUsesSupporterOnlyFiltersWithResults() { AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); From b7c4fe2052a38f30aa1789f8c8b9103660b51b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:24:12 +0200 Subject: [PATCH 1086/1621] Rewrite test helpers to also handle clearing filters --- .../Online/TestSceneBeatmapListingOverlay.cs | 100 +++++++++--------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 16122e496f..66e7248207 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.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 NUnit.Framework; @@ -14,6 +15,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Rulesets; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online @@ -77,27 +79,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); - // test non-supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); notFoundPlaceholderShown(); - // test non-supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); - // test non-supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both RankAchieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); } @@ -107,27 +109,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); - // test supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); notFoundPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); notFoundPlaceholderShown(); - // test supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); notFoundPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); - // test supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both Rank Achieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); notFoundPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); } @@ -137,27 +139,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); - // test non-supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); noPlaceholderShown(); - // test non-supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); - // test non-supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both Rank Achieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); } @@ -167,27 +169,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); - // test supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); noPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); noPlaceholderShown(); - // test supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); noPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); - // test supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both Rank Achieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); noPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); } @@ -200,18 +202,18 @@ namespace osu.Game.Tests.Visual.Online searchControl.Query.TriggerChange(); } - private void toggleRankFilter(Scoring.ScoreRank rank) + private void setRankAchievedFilter(ScoreRank[] ranks) { - AddStep("toggle Rank Achieved filter", () => + AddStep($"set Rank Achieved filter to [{string.Join(',', ranks)}]", () => { searchControl.Ranks.Clear(); - searchControl.Ranks.Add(rank); + searchControl.Ranks.AddRange(ranks); }); } - private void toggleSupporterOnlyPlayedFilter(SearchPlayed played) + private void setPlayedFilter(SearchPlayed played) { - AddStep("toggle Played filter", () => searchControl.Played.Value = played); + AddStep($"set Played filter to {played}", () => searchControl.Played.Value = played); } private void supporterRequiredPlaceholderShown() From 709e555566a80bcc9a7623c6721c99c645da2e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:27:15 +0200 Subject: [PATCH 1087/1621] Rename test steps for legibility --- .../Visual/Online/TestSceneBeatmapListingOverlay.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 66e7248207..5bfb676f81 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -218,17 +218,19 @@ namespace osu.Game.Tests.Visual.Online private void supporterRequiredPlaceholderShown() { - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("\"supporter required\" placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } private void notFoundPlaceholderShown() { - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("\"no maps found\" placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } private void noPlaceholderShown() { - AddUntilStep("no placeholder shown", () => !overlay.ChildrenOfType().Any() && !overlay.ChildrenOfType().Any()); + AddUntilStep("no placeholder shown", () => + !overlay.ChildrenOfType().Any() + && !overlay.ChildrenOfType().Any()); } private class TestAPIBeatmapSet : APIBeatmapSet From b56dd7ff25db96dbcd73c1e0f651987bda726e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:31:26 +0200 Subject: [PATCH 1088/1621] Fix naming and xmldocs in new beatmap search result structures --- .../BeatmapListingFilterControl.cs | 43 +++++++++++++------ osu.Game/Overlays/BeatmapListingOverlay.cs | 4 +- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index b6a0846407..d80ef075e9 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -26,8 +26,6 @@ namespace osu.Game.Overlays.BeatmapListing { /// /// Fired when a search finishes. - /// SearchFinished.Type = ResultsReturned when results returned. Contains only new items in the case of pagination. - /// SearchFinished.Type = SupporterOnlyFilter when a non-supporter user applied supporter-only filters. /// public Action SearchFinished; @@ -216,7 +214,7 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse = response; getSetsRequest = null; - // check if an non-supporter user used supporter-only filters + // check if a non-supporter used supporter-only filters if (!api.LocalUser.Value.IsSupporter) { List filters = new List(); @@ -229,7 +227,7 @@ namespace osu.Game.Overlays.BeatmapListing if (filters.Any()) { - SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); + SearchFinished?.Invoke(SearchResult.SupporterOnlyFilters(filters)); return; } } @@ -260,21 +258,40 @@ namespace osu.Game.Overlays.BeatmapListing base.Dispose(isDisposing); } + /// + /// Indicates the type of result of a user-requested beatmap search. + /// public enum SearchResultType { - // returned with Results + /// + /// Actual results have been returned from API. + /// ResultsReturned, - // non-supporter user applied supporter-only filters - SupporterOnlyFilter + + /// + /// The user is not a supporter, but used supporter-only search filters. + /// + SupporterOnlyFilters } - // Results only valid when Type == ResultsReturned - // Filters only valid when Type == SupporterOnlyFilter + /// + /// Describes the result of a user-requested beatmap search. + /// public struct SearchResult { public SearchResultType Type { get; private set; } + + /// + /// Contains the beatmap sets returned from API. + /// Valid for read if and only if is . + /// public List Results { get; private set; } - public List Filters { get; private set; } + + /// + /// Contains the names of supporter-only filters requested by the user. + /// Valid for read if and only if is . + /// + public List SupporterOnlyFiltersUsed { get; private set; } public static SearchResult ResultsReturned(List results) => new SearchResult { @@ -282,10 +299,10 @@ namespace osu.Game.Overlays.BeatmapListing Results = results }; - public static SearchResult SupporterOnlyFilter(List filters) => new SearchResult + public static SearchResult SupporterOnlyFilters(List filters) => new SearchResult { - Type = SearchResultType.SupporterOnlyFilter, - Filters = filters + Type = SearchResultType.SupporterOnlyFilters, + SupporterOnlyFiltersUsed = filters }; } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index c2ba3d5bc0..5489f0277f 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -123,9 +123,9 @@ namespace osu.Game.Overlays private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult) { // non-supporter user used supporter-only filters - if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilter) + if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters) { - supporterRequiredContent.UpdateText(searchResult.Filters); + supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed); addContentToPlaceholder(supporterRequiredContent); return; } From 9061ab0a278e058704db7dd4c2ffa6ea476463d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:40:54 +0200 Subject: [PATCH 1089/1621] Update/reword comments in listing overlay --- osu.Game/Overlays/BeatmapListingOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5489f0277f..460b4ba4c9 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -122,7 +122,6 @@ namespace osu.Game.Overlays private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult) { - // non-supporter user used supporter-only filters if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters) { supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed); @@ -185,7 +184,7 @@ namespace osu.Game.Overlays if (lastContent == notFoundContent || lastContent == supporterRequiredContent) { - // the placeholder may be used multiple times, so don't expire/dispose it. + // the placeholders may be used multiple times, so don't expire/dispose them. transform.Schedule(() => panelTarget.Remove(lastContent)); } else @@ -253,7 +252,8 @@ namespace osu.Game.Overlays } } - // using string literals as there's no proper processing for LocalizeStrings yet + // TODO: localisation requires Text/LinkFlowContainer support for localising strings with links inside + // (https://github.com/ppy/osu-framework/issues/4530) public class SupporterRequiredDrawable : CompositeDrawable { private LinkFlowContainer supporterRequiredText; From 51147405c59e7c01fd035efa55f8ac68fadd755d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 20:44:39 +0200 Subject: [PATCH 1090/1621] Make || and && priority explicit --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 07ca470e62..f9897ea20c 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Edit.Checks // Only generating issues on hitsounded or last objects ensures we get one issue per long period. // If there are no hitsounds we let the "No hitsounds" template take precedence. - if (hasHitsound(hitObject) || isLastObject && hasHitsounds) + if (hasHitsound(hitObject) || (isLastObject && hasHitsounds)) { var timeWithoutHitsounds = time - lastHitsoundTime; From f78cc9397eff96d67f4b198ffd758934ba8b09c3 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 20:45:31 +0200 Subject: [PATCH 1091/1621] Factor out edge type logic --- .../Rulesets/Edit/Checks/CheckMutedObjects.cs | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index cbe7c7fbab..23d89a2f44 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -22,6 +22,14 @@ namespace osu.Game.Rulesets.Edit.Checks /// private const int low_volume_threshold = 20; + private enum EdgeType + { + Head, + Repeat, + Tail, + None + } + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Low volume hitobjects"); public IEnumerable PossibleTemplates => new IssueTemplate[] @@ -58,37 +66,43 @@ namespace osu.Game.Rulesets.Edit.Checks int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume > 0 ? sample.Volume : sampledHitObject.SampleControlPoint.SampleVolume); double samplePlayTime = sampledHitObject.GetEndTime(); - bool head = Precision.AlmostEquals(samplePlayTime, hitObject.StartTime, 1f); - bool tail = Precision.AlmostEquals(samplePlayTime, hitObject.GetEndTime(), 1f); - bool repeat = false; - - if (hitObject is IHasRepeats hasRepeats && !head && !tail) - { - double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); - repeat = Precision.AlmostEquals((samplePlayTime - hitObject.StartTime) % spanDuration, 0f, 1f); - } - + EdgeType edgeType = getEdgeAtTime(hitObject, samplePlayTime); // We only care about samples played on the edges of objects, not ones like spinnerspin or slidertick. - if (!head && !tail && !repeat) + if (edgeType == EdgeType.None) yield break; - string postfix = null; - if (hitObject is IHasDuration) - postfix = head ? "head" : tail ? "tail" : "repeat"; + string postfix = hitObject is IHasDuration ? edgeType.ToString().ToLower() : null; if (maxVolume <= muted_threshold) { - if (head) + if (edgeType == EdgeType.Head) yield return new IssueTemplateMutedActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); else yield return new IssueTemplateMutedPassive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); } - else if (maxVolume <= low_volume_threshold && head) + else if (maxVolume <= low_volume_threshold && edgeType == EdgeType.Head) { yield return new IssueTemplateLowVolumeActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); } } + private EdgeType getEdgeAtTime(HitObject hitObject, double time) + { + if (Precision.AlmostEquals(time, hitObject.StartTime, 1f)) + return EdgeType.Head; + if (Precision.AlmostEquals(time, hitObject.GetEndTime(), 1f)) + return EdgeType.Tail; + + if (hitObject is IHasRepeats hasRepeats) + { + double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); + if (Precision.AlmostEquals((time - hitObject.StartTime) % spanDuration, 0f, 1f)) + return EdgeType.Repeat; + } + + return EdgeType.None; + } + public abstract class IssueTemplateMuted : IssueTemplate { protected IssueTemplateMuted(ICheck check, IssueType type, string unformattedMessage) From 5642d321b70f55550b4bc992c0ccf992514cf5b9 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:43:08 +0200 Subject: [PATCH 1092/1621] Fix comments in few hitsounds check tests --- osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 8edc6d096e..7561e94d2e 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Editing.Checks hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } - // Should prompt one warning between 1st and 11th, and another between 11th and 20th. + // Should prompt one warning between 1st and 16th, and another between 16th and 31st. assertLongPeriodWarning(hitObjects, count: 2); } @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Editing.Checks hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } - // Should prompt one problem between 1st and 40th, and another between 40th and 80th. + // Should prompt one problem between 1st and 41st, and another between 41st and 81st. assertLongPeriodProblem(hitObjects, count: 2); } @@ -140,7 +140,6 @@ namespace osu.Game.Tests.Editing.Checks hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } - // Should prompt one problem between 1st and 40th, and another between 40th and 80th. assertNoHitsounds(hitObjects); } From 191308434215634a96f88dc3501bacee9a2da354 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:48:28 +0200 Subject: [PATCH 1093/1621] Use `HitSampleInfo.AllAdditions` instead of new list --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index f9897ea20c..acdac83141 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -32,8 +32,6 @@ namespace osu.Game.Rulesets.Edit.Checks private const int warning_threshold_objects = 4; private const int problem_threshold_objects = 16; - private static readonly string[] hitsound_types = { HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_FINISH }; - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Few or no hitsounds"); public IEnumerable PossibleTemplates => new IssueTemplate[] @@ -111,7 +109,7 @@ namespace osu.Game.Rulesets.Edit.Checks private bool hasHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitsound); private bool couldHaveHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitnormal); - private bool isHitsound(HitSampleInfo sample) => hitsound_types.Any(sample.Name.Contains); + private bool isHitsound(HitSampleInfo sample) => HitSampleInfo.AllAdditions.Any(sample.Name.Contains); private bool isHitnormal(HitSampleInfo sample) => sample.Name.Contains(HitSampleInfo.HIT_NORMAL); public abstract class IssueTemplateLongPeriod : IssueTemplate From d29e6f46953624bbfac6dbea4f5c0929a0071ab6 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:49:06 +0200 Subject: [PATCH 1094/1621] Add negligible template to `PossibleTemplates` --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index acdac83141..1de3652a39 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Edit.Checks { new IssueTemplateLongPeriodProblem(this), new IssueTemplateLongPeriodWarning(this), + new IssueTemplateLongPeriodNegligible(this), new IssueTemplateNoHitsounds(this) }; From 5bc08ebadb95ff4c215a9f7214dfde7d66a11031 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:49:25 +0200 Subject: [PATCH 1095/1621] Rename `hasHitsounds` -> `mapHasHitsounds` --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 1de3652a39..8d8243938a 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateNoHitsounds(this) }; - private bool hasHitsounds; + private bool mapHasHitsounds; private int objectsWithoutHitsounds; private double lastHitsoundTime; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (!context.Beatmap.HitObjects.Any()) yield break; - hasHitsounds = false; + mapHasHitsounds = false; objectsWithoutHitsounds = 0; lastHitsoundTime = context.Beatmap.HitObjects.First().StartTime; @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Edit.Checks yield return issue; } - if (!hasHitsounds) + if (!mapHasHitsounds) yield return new IssueTemplateNoHitsounds(this).Create(); } @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Edit.Checks // Only generating issues on hitsounded or last objects ensures we get one issue per long period. // If there are no hitsounds we let the "No hitsounds" template take precedence. - if (hasHitsound(hitObject) || (isLastObject && hasHitsounds)) + if (hasHitsound(hitObject) || (isLastObject && mapHasHitsounds)) { var timeWithoutHitsounds = time - lastHitsoundTime; @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (hasHitsound(hitObject)) { - hasHitsounds = true; + mapHasHitsounds = true; objectsWithoutHitsounds = 0; lastHitsoundTime = time; } From 4796b1b2089f6e5c64449bbb01e81182231491fd Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 00:04:30 +0200 Subject: [PATCH 1096/1621] Use local variables for `hasHitsound` & `couldHaveHitsound` --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 8d8243938a..1fca6b26a4 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -82,10 +82,12 @@ namespace osu.Game.Rulesets.Edit.Checks private IEnumerable applyHitsoundUpdate(HitObject hitObject, bool isLastObject = false) { var time = hitObject.GetEndTime(); + bool hasHitsound = hitObject.Samples.Any(isHitsound); + bool couldHaveHitsound = hitObject.Samples.Any(isHitnormal); // Only generating issues on hitsounded or last objects ensures we get one issue per long period. // If there are no hitsounds we let the "No hitsounds" template take precedence. - if (hasHitsound(hitObject) || (isLastObject && mapHasHitsounds)) + if (hasHitsound || (isLastObject && mapHasHitsounds)) { var timeWithoutHitsounds = time - lastHitsoundTime; @@ -97,19 +99,16 @@ namespace osu.Game.Rulesets.Edit.Checks yield return new IssueTemplateLongPeriodNegligible(this).Create(lastHitsoundTime, timeWithoutHitsounds); } - if (hasHitsound(hitObject)) + if (hasHitsound) { mapHasHitsounds = true; objectsWithoutHitsounds = 0; lastHitsoundTime = time; } - else if (couldHaveHitsound(hitObject)) + else if (couldHaveHitsound) ++objectsWithoutHitsounds; } - private bool hasHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitsound); - private bool couldHaveHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitnormal); - private bool isHitsound(HitSampleInfo sample) => HitSampleInfo.AllAdditions.Any(sample.Name.Contains); private bool isHitnormal(HitSampleInfo sample) => sample.Name.Contains(HitSampleInfo.HIT_NORMAL); From 0c0fd291d9381d718668e0588d6fb5dee60e91c7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 01:25:03 +0200 Subject: [PATCH 1097/1621] Order hitobjects by endtime --- .../Rulesets/Edit/Checks/CheckFewHitsounds.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 1fca6b26a4..5185ba6c99 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -55,18 +55,23 @@ namespace osu.Game.Rulesets.Edit.Checks objectsWithoutHitsounds = 0; lastHitsoundTime = context.Beatmap.HitObjects.First().StartTime; - var hitObjectCount = context.Beatmap.HitObjects.Count; + var hitObjectsIncludingNested = new List(); + + foreach (var hitObject in context.Beatmap.HitObjects) + { + // Samples play on the end of objects. Some objects have nested objects to accomplish playing them elsewhere (e.g. slider head/repeat). + foreach (var nestedHitObject in hitObject.NestedHitObjects) + hitObjectsIncludingNested.Add(nestedHitObject); + + hitObjectsIncludingNested.Add(hitObject); + } + + var hitObjectsByEndTime = hitObjectsIncludingNested.OrderBy(o => o.GetEndTime()).ToList(); + var hitObjectCount = hitObjectsByEndTime.Count; for (int i = 0; i < hitObjectCount; ++i) { - var hitObject = context.Beatmap.HitObjects[i]; - - // Samples play on the end of objects. Some objects have nested objects to accomplish playing them elsewhere (e.g. slider head/repeat). - foreach (var nestedHitObject in hitObject.NestedHitObjects) - { - foreach (var issue in applyHitsoundUpdate(nestedHitObject)) - yield return issue; - } + var hitObject = hitObjectsByEndTime[i]; // This is used to perform an update at the end so that the period after the last hitsounded object can be an issue. bool isLastObject = i == hitObjectCount - 1; From 2cd7eda3c4d62e7b7898c7a60de3d78ea5447b5f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 02:30:12 +0200 Subject: [PATCH 1098/1621] Add "or equal to" to volume threshold xmldocs --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 23d89a2f44..2ac1efc636 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -13,12 +13,12 @@ namespace osu.Game.Rulesets.Edit.Checks public class CheckMutedObjects : ICheck { /// - /// Volume percentages lower than this are typically inaudible. + /// Volume percentages lower than or equal to this are typically inaudible. /// private const int muted_threshold = 5; /// - /// Volume percentages lower than this can sometimes be inaudible depending on sample used and music volume. + /// Volume percentages lower than or equal to this can sometimes be inaudible depending on sample used and music volume. /// private const int low_volume_threshold = 20; From 4cfa0ae5ec79bf2e1922132f23515baa374f2b4e Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 03:26:35 +0200 Subject: [PATCH 1099/1621] Improve precision for repeat edges --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 2ac1efc636..c743b5693e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.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.Utils; @@ -96,7 +97,9 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitObject is IHasRepeats hasRepeats) { double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); - if (Precision.AlmostEquals((time - hitObject.StartTime) % spanDuration, 0f, 1f)) + double spans = (time - hitObject.StartTime) / spanDuration; + + if (Precision.AlmostEquals(spans, Math.Ceiling(spans)) || Precision.AlmostEquals(spans, Math.Floor(spans))) return EdgeType.Repeat; } From d1f852d102489ae8ae69069dc5ae1416ea0927da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 13:06:20 +0900 Subject: [PATCH 1100/1621] Make `Populate` abstract to avoid unnecessary base call async complexity --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 4 ++++ osu.Game/Skinning/SkinManager.cs | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 8efd451857..c1a4a6e18a 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -727,7 +727,7 @@ namespace osu.Game.Database /// The model to populate. /// The archive to use as a reference for population. May be null. /// An optional cancellation token. - protected virtual Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default) => Task.CompletedTask; + protected abstract Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default); /// /// Perform any final actions before the import to database executes. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9d3b952ada..d5bea0affc 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Linq.Expressions; using System.Threading; +using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.Bindables; @@ -72,6 +73,9 @@ namespace osu.Game.Scoring } } + protected override Task Populate(ScoreInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) + => Task.CompletedTask; + protected override void ExportModelTo(ScoreInfo model, Stream outputStream) { var file = model.Files.SingleOrDefault(); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 4cde4cd2b8..645c943d09 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -142,16 +142,16 @@ namespace osu.Game.Skinning return base.ComputeHash(item, reader); } - protected override async Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) + protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) { - await base.Populate(model, archive, cancellationToken).ConfigureAwait(false); - var instance = GetSkin(model); model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo(); if (model.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true) populateMetadata(model, instance); + + return Task.CompletedTask; } private void populateMetadata(SkinInfo item, Skin instance) From 46f8100f4371b64e2bda9b484493597b4a868bf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 13:06:47 +0900 Subject: [PATCH 1101/1621] Remove overly verbose logging during beatmap imports --- osu.Game/Beatmaps/BeatmapManager.cs | 2 -- osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 1 - 2 files changed, 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..86c8fb611f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -191,8 +191,6 @@ namespace osu.Game.Beatmaps { var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); - LogForModel(beatmapSet, $"Validating online IDs for {beatmapSet.Beatmaps.Count} beatmaps..."); - // ensure all IDs are unique if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1)) { diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 5dff4fe282..7824205257 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -48,7 +48,6 @@ namespace osu.Game.Beatmaps public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) { - LogForModel(beatmapSet, "Performing online lookups..."); return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray()); } From 15af28d2a0af88ac7f167341d1d438121ccf3a62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 14:48:51 +0900 Subject: [PATCH 1102/1621] Remove comparison of online beatmap IDs during dedupe checks --- .../Beatmaps/IO/ImportBeatmapTest.cs | 35 ------------------- osu.Game/Beatmaps/BeatmapManager.cs | 12 ------- osu.Game/Database/ArchiveModelManager.cs | 4 +-- 3 files changed, 2 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0d117f8755..2087bdf144 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -439,41 +439,6 @@ namespace osu.Game.Tests.Beatmaps.IO } } - [TestCase(true)] - [TestCase(false)] - public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) - { - try - { - var osu = LoadOsuIntoHost(host); - - var imported = await LoadOszIntoOsu(osu); - - if (set) - imported.OnlineBeatmapSetID = 1234; - else - imported.Beatmaps.First().OnlineBeatmapID = 1234; - - osu.Dependencies.Get().Update(imported); - - deleteBeatmapSet(imported, osu); - - var importedSecondTime = await LoadOszIntoOsu(osu); - - // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched) - Assert.IsTrue(imported.ID != importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); - } - finally - { - host.Exit(); - } - } - } - [Test] public async Task TestImportWithDuplicateBeatmapIDs() { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..221774b018 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,18 +319,6 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); - protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) - { - if (!base.CanReuseExisting(existing, import)) - return false; - - var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); - var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); - - // force re-import if we are not in a sane state. - return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds); - } - /// /// Returns a list of all usable s. /// diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 8efd451857..29c83b4699 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -377,7 +377,7 @@ namespace osu.Game.Database if (existing != null) { - if (CanReuseExisting(existing, item)) + if (canReuseExisting(existing, item)) { Undelete(existing); LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); @@ -751,7 +751,7 @@ namespace osu.Game.Database /// The existing model. /// The newly imported model. /// Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import. - protected virtual bool CanReuseExisting(TModel existing, TModel import) => + private bool canReuseExisting(TModel existing, TModel import) => // for the best or worst, we copy and import files of a new import before checking whether // it is a duplicate. so to check if anything has changed, we can just compare all FileInfo IDs. getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) && From e493685c14acd80e430092e62700bcc75e59f618 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 16:34:40 +0900 Subject: [PATCH 1103/1621] Add optimised existing check earlier in import process --- osu.Game/Database/ArchiveModelManager.cs | 61 +++++++++++++++++++----- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 29c83b4699..80faa64953 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -317,7 +317,11 @@ namespace osu.Game.Database /// protected virtual string ComputeHash(TModel item, ArchiveReader reader = null) { - // for now, concatenate all .osu files in the set to create a unique hash. + if (reader != null) + // fast hashing for cases where the item's files may not be populated. + return computeHashFast(reader); + + // for now, concatenate all hashable files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f.Filename)) @@ -329,12 +333,25 @@ namespace osu.Game.Database if (hashable.Length > 0) return hashable.ComputeSHA2Hash(); - if (reader != null) - return reader.Name.ComputeSHA2Hash(); - return item.Hash; } + private string computeHashFast(ArchiveReader reader) + { + MemoryStream hashable = new MemoryStream(); + + foreach (var file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) + { + using (Stream s = reader.GetStream(file)) + s.CopyTo(hashable); + } + + if (hashable.Length > 0) + return hashable.ComputeSHA2Hash(); + + return reader.Name.ComputeSHA2Hash(); + } + /// /// Silently import an item from a . /// @@ -348,6 +365,21 @@ namespace osu.Game.Database delayEvents(); + if (archive != null) + { + // fast bail to improve large import performance. + item.Hash = computeHashFast(archive); + + var fastExisting = CheckForExisting(item); + + if (fastExisting != null) + { + // bare minimum comparisons + if (getFilenames(fastExisting.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened))) + return fastExisting; + } + } + void rollback() { if (!Delete(item)) @@ -644,18 +676,14 @@ namespace osu.Game.Database { var fileInfos = new List(); - string prefix = reader.Filenames.GetCommonPrefix(); - if (!(prefix.EndsWith('/') || prefix.EndsWith('\\'))) - prefix = string.Empty; - // import files to manager - foreach (string file in reader.Filenames) + foreach (var filenames in getShortenedFilenames(reader)) { - using (Stream s = reader.GetStream(file)) + using (Stream s = reader.GetStream(filenames.original)) { fileInfos.Add(new TFileModel { - Filename = file.Substring(prefix.Length).ToStandardisedPath(), + Filename = filenames.shortened, FileInfo = files.Add(s) }); } @@ -664,6 +692,17 @@ namespace osu.Game.Database return fileInfos; } + private IEnumerable<(string original, string shortened)> getShortenedFilenames(ArchiveReader reader) + { + string prefix = reader.Filenames.GetCommonPrefix(); + if (!(prefix.EndsWith('/') || prefix.EndsWith('\\'))) + prefix = string.Empty; + + // import files to manager + foreach (string file in reader.Filenames) + yield return (file, file.Substring(prefix.Length).ToStandardisedPath()); + } + #region osu-stable import /// From 44f875b802f8d958d58cb34b065474fcc954b5ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 16:35:13 +0900 Subject: [PATCH 1104/1621] Bypass optimised existing check in `SkinManager` (due to custom hashing function) --- osu.Game/Database/ArchiveModelManager.cs | 8 +++++++- osu.Game/Skinning/SkinManager.cs | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 80faa64953..22e5486909 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -309,6 +309,12 @@ namespace osu.Game.Database Logger.Log($"{prefix} {message}", LoggingTarget.Database); } + /// + /// Whether the implementation overrides with a custom implementation. + /// Custom has implementations must bypass the early exit in the import flow (see usage). + /// + protected virtual bool HasCustomHashFunction => false; + /// /// Create a SHA-2 hash from the provided archive based on file content of all files matching . /// @@ -365,7 +371,7 @@ namespace osu.Game.Database delayEvents(); - if (archive != null) + if (archive != null && !HasCustomHashFunction) { // fast bail to improve large import performance. item.Hash = computeHashFast(archive); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 4cde4cd2b8..43cf6b6874 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -125,6 +125,8 @@ namespace osu.Game.Skinning private const string unknown_creator_string = "Unknown"; + protected override bool HasCustomHashFunction => true; + protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null) { // we need to populate early to create a hash based off skin.ini contents From 9120321731baac949045283dd0057ab1e8a723bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 16:48:42 +0900 Subject: [PATCH 1105/1621] Add comments mentioning shortcomings and avoid potential double check --- osu.Game/Database/ArchiveModelManager.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 22e5486909..ed5b54f446 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -371,18 +371,25 @@ namespace osu.Game.Database delayEvents(); + bool checkedExisting = false; + TModel existing = null; + if (archive != null && !HasCustomHashFunction) { - // fast bail to improve large import performance. + // this is a fast bail condition to improve large import performance. item.Hash = computeHashFast(archive); - var fastExisting = CheckForExisting(item); + checkedExisting = true; + existing = CheckForExisting(item); - if (fastExisting != null) + if (existing != null) { // bare minimum comparisons - if (getFilenames(fastExisting.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened))) - return fastExisting; + // + // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. + // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. + if (getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) + return existing; } } @@ -411,7 +418,8 @@ namespace osu.Game.Database { if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}"); - var existing = CheckForExisting(item); + if (!checkedExisting) + existing = CheckForExisting(item); if (existing != null) { From f2164049526e8c94e40ad4db5ec96edecdc257d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 20:22:48 +0900 Subject: [PATCH 1106/1621] Fix missing undelete call on using existing --- osu.Game/Database/ArchiveModelManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index ed5b54f446..7ea6fe067c 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -389,7 +389,10 @@ namespace osu.Game.Database // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. if (getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) + { + Undelete(existing); return existing; + } } } From cd9aa38d3debcc69730ef7e89f5330d463b2c634 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 20:24:16 +0900 Subject: [PATCH 1107/1621] Add back ignore cases for intentionally broken tests --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 2087bdf144..79c85f6c61 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -192,6 +192,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] + [Ignore("intentionally broken by import optimisations")] public async Task TestImportThenImportWithChangedFile() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) @@ -294,6 +295,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] + [Ignore("intentionally broken by import optimisations")] public async Task TestImportCorruptThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. From f470b7095d9e6aafa201a9abf1ba098093ede571 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 20:36:01 +0900 Subject: [PATCH 1108/1621] Move private method down in class --- osu.Game/Database/ArchiveModelManager.cs | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 7ea6fe067c..9b89037334 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -342,22 +342,6 @@ namespace osu.Game.Database return item.Hash; } - private string computeHashFast(ArchiveReader reader) - { - MemoryStream hashable = new MemoryStream(); - - foreach (var file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) - { - using (Stream s = reader.GetStream(file)) - s.CopyTo(hashable); - } - - if (hashable.Length > 0) - return hashable.ComputeSHA2Hash(); - - return reader.Name.ComputeSHA2Hash(); - } - /// /// Silently import an item from a . /// @@ -686,6 +670,22 @@ namespace osu.Game.Database } } + private string computeHashFast(ArchiveReader reader) + { + MemoryStream hashable = new MemoryStream(); + + foreach (var file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) + { + using (Stream s = reader.GetStream(file)) + s.CopyTo(hashable); + } + + if (hashable.Length > 0) + return hashable.ComputeSHA2Hash(); + + return reader.Name.ComputeSHA2Hash(); + } + /// /// Create all required s for the provided archive, adding them to the global file store. /// From e755dcc34dffbaa8b6415fa9adf5fe61580cf940 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 20:37:12 +0900 Subject: [PATCH 1109/1621] Add log method for new flow --- osu.Game/Database/ArchiveModelManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 9b89037334..e1202ed95f 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -374,6 +374,7 @@ namespace osu.Game.Database // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. if (getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) { + LogForModel(item, $"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); Undelete(existing); return existing; } From c2ceb83bbb20c53edff6c4973fb45e48a984cad2 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:16:40 +0200 Subject: [PATCH 1110/1621] Move `MockNestedHitObject` to own class --- .../Editing/Checks/CheckMutedObjectsTest.cs | 28 --------------- .../Editing/Checks/MockNestableHitObject.cs | 36 +++++++++++++++++++ 2 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs index 4bfe62a64d..41a8f72305 100644 --- a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; using NUnit.Framework; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -11,7 +10,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; @@ -27,32 +25,6 @@ namespace osu.Game.Tests.Editing.Checks private const int volume_low = 15; private const int volume_muted = 5; - private sealed class MockNestableHitObject : HitObject, IHasDuration - { - private readonly IEnumerable toBeNested; - - public MockNestableHitObject(IEnumerable toBeNested, double startTime, double endTime) - { - this.toBeNested = toBeNested; - StartTime = startTime; - EndTime = endTime; - } - - protected override void CreateNestedHitObjects(CancellationToken cancellationToken) - { - foreach (var hitObject in toBeNested) - AddNested(hitObject); - } - - public double EndTime { get; } - - public double Duration - { - get => EndTime - StartTime; - set => throw new System.NotImplementedException(); - } - } - [SetUp] public void Setup() { diff --git a/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs b/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs new file mode 100644 index 0000000000..29938839d3 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs @@ -0,0 +1,36 @@ +// 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.Threading; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Tests.Editing.Checks +{ + public sealed class MockNestableHitObject : HitObject, IHasDuration + { + private readonly IEnumerable toBeNested; + + public MockNestableHitObject(IEnumerable toBeNested, double startTime, double endTime) + { + this.toBeNested = toBeNested; + StartTime = startTime; + EndTime = endTime; + } + + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) + { + foreach (var hitObject in toBeNested) + AddNested(hitObject); + } + + public double EndTime { get; } + + public double Duration + { + get => EndTime - StartTime; + set => throw new System.NotImplementedException(); + } + } +} From 1d5bff166043e0b7b41c98603c65af3e411ec115 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:26:52 +0200 Subject: [PATCH 1111/1621] Add concurrent hitobjects test for few hitsounds check See https://github.com/ppy/osu/pull/13669#discussion_r659314980 --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 7561e94d2e..d681c3fd3d 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; @@ -143,6 +144,35 @@ namespace osu.Game.Tests.Editing.Checks assertNoHitsounds(hitObjects); } + [Test] + public void TestConcurrentObjects() + { + var hitObjects = new List(); + + var notHitsounded = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + var hitsounded = new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL), + new HitSampleInfo(HitSampleInfo.HIT_FINISH) + }; + + var ticks = new List(); + for (int i = 1; i < 10; ++i) + ticks.Add(new SliderTick { StartTime = 5000 * i, Samples = hitsounded }); + + var nested = new MockNestableHitObject(ticks.ToList(), 0, 50000) + { + Samples = notHitsounded + }; + nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + hitObjects.Add(nested); + + for (int i = 1; i <= 6; ++i) + hitObjects.Add(new HitCircle { StartTime = 10000 * i, Samples = notHitsounded }); + + assertOk(hitObjects); + } + private void assertOk(List hitObjects) { Assert.That(check.Run(getContext(hitObjects)), Is.Empty); From a4a5325b73e8e6108137aacd40dbdf93c074f980 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:39:31 +0200 Subject: [PATCH 1112/1621] Improve acceptable difference for repeat edges Likelihood that `spanDuration` is greater than E+7 is quite low in any realistic case, so this should work fine. --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index c743b5693e..0559a8b0cd 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -98,9 +98,13 @@ namespace osu.Game.Rulesets.Edit.Checks { double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); double spans = (time - hitObject.StartTime) / spanDuration; + double acceptableDifference = 1 / spanDuration; // 1 ms of acceptable difference, as with head/tail above. - if (Precision.AlmostEquals(spans, Math.Ceiling(spans)) || Precision.AlmostEquals(spans, Math.Floor(spans))) + if (Precision.AlmostEquals(spans, Math.Ceiling(spans), acceptableDifference) || + Precision.AlmostEquals(spans, Math.Floor(spans), acceptableDifference)) + { return EdgeType.Repeat; + } } return EdgeType.None; From 9f9e96ce9ee797024585af8515338f67bc0175d6 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:40:09 +0200 Subject: [PATCH 1113/1621] Add check for `spanDuration` <= 0 prior to division --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 0559a8b0cd..a4ff921b7e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -97,6 +97,10 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitObject is IHasRepeats hasRepeats) { double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); + if (spanDuration <= 0) + // Prevents undefined behaviour in cases like where zero/negative-length sliders/hold notes exist. + return EdgeType.None; + double spans = (time - hitObject.StartTime) / spanDuration; double acceptableDifference = 1 / spanDuration; // 1 ms of acceptable difference, as with head/tail above. From 1dbac76da5c4366bab40f1962faa40e0322d0c5b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:57:41 +0200 Subject: [PATCH 1114/1621] Use local variables for common sample lists --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index d681c3fd3d..21e274c2c0 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -20,10 +20,19 @@ namespace osu.Game.Tests.Editing.Checks { private CheckFewHitsounds check; + private List notHitsounded; + private List hitsounded; + [SetUp] public void Setup() { check = new CheckFewHitsounds(); + notHitsounded = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + hitsounded = new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL), + new HitSampleInfo(HitSampleInfo.HIT_FINISH) + }; } [Test] @@ -80,10 +89,7 @@ namespace osu.Game.Tests.Editing.Checks for (int i = 0; i < 30; ++i) { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - if (i % 8 == 0) - samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + var samples = i % 8 == 0 ? hitsounded : notHitsounded; hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } @@ -98,10 +104,7 @@ namespace osu.Game.Tests.Editing.Checks for (int i = 0; i < 30; ++i) { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - if (i == 0 || i == 15) - samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + var samples = (i == 0 || i == 15) ? hitsounded : notHitsounded; hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } @@ -117,10 +120,7 @@ namespace osu.Game.Tests.Editing.Checks for (int i = 0; i < 80; ++i) { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - if (i == 40) - samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + var samples = i == 40 ? hitsounded : notHitsounded; hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } @@ -135,11 +135,7 @@ namespace osu.Game.Tests.Editing.Checks var hitObjects = new List(); for (int i = 0; i < 20; ++i) - { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); - } + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = notHitsounded }); assertNoHitsounds(hitObjects); } @@ -149,13 +145,6 @@ namespace osu.Game.Tests.Editing.Checks { var hitObjects = new List(); - var notHitsounded = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - var hitsounded = new List - { - new HitSampleInfo(HitSampleInfo.HIT_NORMAL), - new HitSampleInfo(HitSampleInfo.HIT_FINISH) - }; - var ticks = new List(); for (int i = 1; i < 10; ++i) ticks.Add(new SliderTick { StartTime = 5000 * i, Samples = hitsounded }); From b58644106c188d09802dd8d1e455633193651f5d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:58:00 +0200 Subject: [PATCH 1115/1621] Add nested hitobject tests for few hitsounds check --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 21e274c2c0..cf5b3a42a4 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -140,6 +140,38 @@ namespace osu.Game.Tests.Editing.Checks assertNoHitsounds(hitObjects); } + [Test] + public void TestNestedObjectsHitsounded() + { + var ticks = new List(); + for (int i = 1; i < 16; ++i) + ticks.Add(new SliderTick { StartTime = 1000 * i, Samples = hitsounded }); + + var nested = new MockNestableHitObject(ticks.ToList(), 0, 16000) + { + Samples = hitsounded + }; + nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertOk(new List { nested }); + } + + [Test] + public void TestNestedObjectsRarelyHitsounded() + { + var ticks = new List(); + for (int i = 1; i < 16; ++i) + ticks.Add(new SliderTick { StartTime = 1000 * i, Samples = i == 0 ? hitsounded : notHitsounded }); + + var nested = new MockNestableHitObject(ticks.ToList(), 0, 16000) + { + Samples = hitsounded + }; + nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertLongPeriodWarning(new List { nested }); + } + [Test] public void TestConcurrentObjects() { From 9a96cd4a1df625322e644fc9b708676fbe62cd04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 09:54:18 +0900 Subject: [PATCH 1116/1621] Revert "Remove comparison of online beatmap IDs during dedupe checks" This reverts commit 15af28d2a0af88ac7f167341d1d438121ccf3a62. --- .../Beatmaps/IO/ImportBeatmapTest.cs | 35 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapManager.cs | 12 +++++++ osu.Game/Database/ArchiveModelManager.cs | 4 +-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 79c85f6c61..990e75df81 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -441,6 +441,41 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [TestCase(true)] + [TestCase(false)] + public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) + { + // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) + { + try + { + var osu = LoadOsuIntoHost(host); + + var imported = await LoadOszIntoOsu(osu); + + if (set) + imported.OnlineBeatmapSetID = 1234; + else + imported.Beatmaps.First().OnlineBeatmapID = 1234; + + osu.Dependencies.Get().Update(imported); + + deleteBeatmapSet(imported, osu); + + var importedSecondTime = await LoadOszIntoOsu(osu); + + // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched) + Assert.IsTrue(imported.ID != importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + } + finally + { + host.Exit(); + } + } + } + [Test] public async Task TestImportWithDuplicateBeatmapIDs() { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 221774b018..00af06703d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,6 +319,18 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); + protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) + { + if (!base.CanReuseExisting(existing, import)) + return false; + + var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + + // force re-import if we are not in a sane state. + return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds); + } + /// /// Returns a list of all usable s. /// diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index e1202ed95f..84473f57fe 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -411,7 +411,7 @@ namespace osu.Game.Database if (existing != null) { - if (canReuseExisting(existing, item)) + if (CanReuseExisting(existing, item)) { Undelete(existing); LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); @@ -808,7 +808,7 @@ namespace osu.Game.Database /// The existing model. /// The newly imported model. /// Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import. - private bool canReuseExisting(TModel existing, TModel import) => + protected virtual bool CanReuseExisting(TModel existing, TModel import) => // for the best or worst, we copy and import files of a new import before checking whether // it is a duplicate. so to check if anything has changed, we can just compare all FileInfo IDs. getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) && From 90b87cbb9eaba15ade0585a881ded8978192d871 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 10:11:27 +0900 Subject: [PATCH 1117/1621] Add back unidirectional online id check --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 10 +++------- osu.Game/Beatmaps/BeatmapManager.cs | 8 ++++++++ osu.Game/Database/ArchiveModelManager.cs | 12 +++++++++++- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 990e75df81..e87b82cd2a 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -441,9 +441,7 @@ namespace osu.Game.Tests.Beatmaps.IO } } - [TestCase(true)] - [TestCase(false)] - public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) + public async Task TestImportThenDeleteThenImportWithOnlineIDsMissing() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) @@ -454,10 +452,8 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = await LoadOszIntoOsu(osu); - if (set) - imported.OnlineBeatmapSetID = 1234; - else - imported.Beatmaps.First().OnlineBeatmapID = 1234; + foreach (var b in imported.Beatmaps) + b.OnlineBeatmapID = null; osu.Dependencies.Get().Update(imported); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..f854a5fecb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,6 +319,14 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); + protected override bool CanSkipImport(BeatmapSetInfo existing, BeatmapSetInfo import) + { + if (!base.CanReuseExisting(existing, import)) + return false; + + return existing.Beatmaps.Any(b => b.OnlineBeatmapID != null); + } + protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) { if (!base.CanReuseExisting(existing, import)) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 84473f57fe..6d8b671fd8 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -372,7 +372,8 @@ namespace osu.Game.Database // // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. - if (getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) + if (CanSkipImport(existing, item) && + getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) { LogForModel(item, $"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); Undelete(existing); @@ -801,6 +802,15 @@ namespace osu.Game.Database /// An existing model which matches the criteria to skip importing, else null. protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); + /// + /// Whether inport can be skipped after finding an existing import early in the process. + /// Only valid when is not overridden. + /// + /// The existing model. + /// The newly imported model. + /// Whether to skip this import completely. + protected virtual bool CanSkipImport(TModel existing, TModel import) => true; + /// /// After an existing is found during an import process, the default behaviour is to use/restore the existing /// item and skip the import. This method allows changing that behaviour. From 128f08ccbadaf5a2b027f88b651c8ace8a7696f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 10:42:28 +0900 Subject: [PATCH 1118/1621] Fix test oversights --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index e87b82cd2a..02b1d8bcfc 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -441,10 +441,11 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] public async Task TestImportThenDeleteThenImportWithOnlineIDsMissing() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}")) { try { From 4a557e73a781b4d71f79f797ccfe876c90b9d89c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 10:42:42 +0900 Subject: [PATCH 1119/1621] Add logging to help understand existing case skips better --- osu.Game/Database/ArchiveModelManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 6d8b671fd8..91ffe2966a 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -379,6 +379,8 @@ namespace osu.Game.Database Undelete(existing); return existing; } + + LogForModel(item, $"Found existing (optimised) but failed pre-check."); } } @@ -422,6 +424,7 @@ namespace osu.Game.Database return existing; } + LogForModel(item, $"Found existing but failed re-use check."); Delete(existing); ModelStore.PurgeDeletable(s => s.ID == existing.ID); } From 0cceef8da50264bbfa0c539e183acd04ee1d6e68 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 28 Jun 2021 11:00:07 +0800 Subject: [PATCH 1120/1621] Moved the `string` to `int?` conversion logic into `SettingsNumberBox` --- .../Screens/Editors/RoundEditorScreen.cs | 12 ++-- .../Screens/Editors/SeedingEditorScreen.cs | 14 ++-- .../Screens/Editors/TeamEditorScreen.cs | 12 ++-- .../Overlays/Settings/SettingsNumberBox.cs | 59 +++++++++++++-- osu.Game/Rulesets/Mods/ModRandom.cs | 3 +- osu.Game/Rulesets/Mods/SeedSettingsControl.cs | 72 ------------------- 6 files changed, 72 insertions(+), 100 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/SeedSettingsControl.cs diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index 069ddfa4db..e91eed420e 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -147,7 +147,7 @@ namespace osu.Game.Tournament.Screens.Editors [Resolved] protected IAPIProvider API { get; private set; } - private readonly Bindable beatmapId = new Bindable(); + private readonly Bindable beatmapId = new Bindable(); private readonly Bindable mods = new Bindable(); @@ -220,14 +220,12 @@ namespace osu.Game.Tournament.Screens.Editors [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - beatmapId.Value = Model.ID.ToString(); - beatmapId.BindValueChanged(idString => + beatmapId.Value = Model.ID; + beatmapId.BindValueChanged(idInt => { - int.TryParse(idString.NewValue, out var parsed); + Model.ID = idInt.NewValue ?? 0; - Model.ID = parsed; - - if (idString.NewValue != idString.OldValue) + if (idInt.NewValue != idInt.OldValue) Model.BeatmapInfo = null; if (Model.BeatmapInfo != null) diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index 7bd8d3f6a0..4b50d74dc8 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -147,7 +147,7 @@ namespace osu.Game.Tournament.Screens.Editors [Resolved] protected IAPIProvider API { get; private set; } - private readonly Bindable beatmapId = new Bindable(); + private readonly Bindable beatmapId = new Bindable(); private readonly Bindable score = new Bindable(); @@ -228,16 +228,12 @@ namespace osu.Game.Tournament.Screens.Editors [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - beatmapId.Value = Model.ID.ToString(); - beatmapId.BindValueChanged(idString => + beatmapId.Value = Model.ID; + beatmapId.BindValueChanged(idInt => { - int parsed; + Model.ID = idInt.NewValue ?? 0; - int.TryParse(idString.NewValue, out parsed); - - Model.ID = parsed; - - if (idString.NewValue != idString.OldValue) + if (idInt.NewValue != idInt.OldValue) Model.BeatmapInfo = null; if (Model.BeatmapInfo != null) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index aa1be143ea..22ec2d3398 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -214,7 +214,7 @@ namespace osu.Game.Tournament.Screens.Editors [Resolved] private TournamentGameBase game { get; set; } - private readonly Bindable userId = new Bindable(); + private readonly Bindable userId = new Bindable(); private readonly Container drawableContainer; @@ -278,14 +278,12 @@ namespace osu.Game.Tournament.Screens.Editors [BackgroundDependencyLoader] private void load() { - userId.Value = user.Id.ToString(); - userId.BindValueChanged(idString => + userId.Value = user.Id; + userId.BindValueChanged(idInt => { - int.TryParse(idString.NewValue, out var parsed); + user.Id = idInt.NewValue ?? 0; - user.Id = parsed; - - if (idString.NewValue != idString.OldValue) + if (idInt.NewValue != idInt.OldValue) user.Username = string.Empty; if (!string.IsNullOrEmpty(user.Username)) diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index d4d1fc8610..bc86f2c6dc 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -1,17 +1,68 @@ // 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.UserInterface; namespace osu.Game.Overlays.Settings { - public class SettingsNumberBox : SettingsItem + public class SettingsNumberBox : SettingsItem { - protected override Drawable CreateControl() => new OutlinedNumberBox + protected override Drawable CreateControl() => new NumberControl { - Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true + Margin = new MarginPadding { Top = 5 } }; + + private sealed class NumberControl : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + private readonly OutlinedNumberBox numberBox; + + public Bindable Current + { + get => current; + set + { + current.Current = value; + numberBox.Text = value.Value.ToString(); + } + } + + public NumberControl() + { + AutoSizeAxes = Axes.Y; + + InternalChildren = new[] + { + numberBox = new OutlinedNumberBox + { + Margin = new MarginPadding { Top = 5 }, + RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true + } + }; + + numberBox.Current.BindValueChanged(e => + { + int? value = null; + + if (int.TryParse(e.NewValue, out var intVal)) + value = intVal; + + current.Value = value; + }); + } + + protected override void Update() + { + base.Update(); + if (Current.Value == null) + numberBox.Current.Value = ""; + } + } } } diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index 61297c162d..1f7742b075 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods { @@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.Dice; public override double ScoreMultiplier => 1; - [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SeedSettingsControl))] + [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))] public Bindable Seed { get; } = new Bindable { Default = null, diff --git a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs b/osu.Game/Rulesets/Mods/SeedSettingsControl.cs deleted file mode 100644 index 1eaf31874b..0000000000 --- a/osu.Game/Rulesets/Mods/SeedSettingsControl.cs +++ /dev/null @@ -1,72 +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.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Overlays.Settings; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// A settings control for use by mods which have a customisable seed value. - /// - public class SeedSettingsControl : SettingsItem - { - protected override Drawable CreateControl() => new SeedControl - { - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 5 } - }; - - private sealed class SeedControl : CompositeDrawable, IHasCurrentValue - { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current - { - get => current; - set - { - current.Current = value; - seedNumberBox.Text = value.Value.ToString(); - } - } - - private readonly OutlinedNumberBox seedNumberBox; - - public SeedControl() - { - AutoSizeAxes = Axes.Y; - - InternalChildren = new[] - { - seedNumberBox = new OutlinedNumberBox - { - Margin = new MarginPadding { Top = 5 }, - RelativeSizeAxes = Axes.X, - CommitOnFocusLost = true - } - }; - - seedNumberBox.Current.BindValueChanged(e => - { - int? value = null; - - if (int.TryParse(e.NewValue, out var intVal)) - value = intVal; - - current.Value = value; - }); - } - - protected override void Update() - { - base.Update(); - if (Current.Value == null) - seedNumberBox.Current.Value = ""; - } - } - } -} From 4d6002ab889e449313f2fe6a8d6718377640a9f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 12:13:11 +0900 Subject: [PATCH 1121/1621] Remove redundant string interpolation (and mark all local logging strings as verbatim) --- osu.Game/Database/ArchiveModelManager.cs | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 91ffe2966a..5446c3b851 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -78,7 +78,7 @@ namespace osu.Game.Database private readonly Bindable> itemRemoved = new Bindable>(); - public virtual IEnumerable HandledExtensions => new[] { ".zip" }; + public virtual IEnumerable HandledExtensions => new[] { @".zip" }; protected readonly FileStore Files; @@ -99,7 +99,7 @@ namespace osu.Game.Database ModelStore.ItemUpdated += item => handleEvent(() => itemUpdated.Value = new WeakReference(item)); ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference(item)); - exportStorage = storage.GetStorageForDirectory("exports"); + exportStorage = storage.GetStorageForDirectory(@"exports"); Files = new FileStore(contextFactory, storage); @@ -282,7 +282,7 @@ namespace osu.Game.Database } catch (Exception e) { - LogForModel(model, $"Model creation of {archive.Name} failed.", e); + LogForModel(model, @$"Model creation of {archive.Name} failed.", e); return null; } @@ -375,12 +375,12 @@ namespace osu.Game.Database if (CanSkipImport(existing, item) && getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) { - LogForModel(item, $"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); + LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); Undelete(existing); return existing; } - LogForModel(item, $"Found existing (optimised) but failed pre-check."); + LogForModel(item, @"Found existing (optimised) but failed pre-check."); } } @@ -389,14 +389,14 @@ namespace osu.Game.Database if (!Delete(item)) { // We may have not yet added the model to the underlying table, but should still clean up files. - LogForModel(item, "Dereferencing files for incomplete import."); + LogForModel(item, @"Dereferencing files for incomplete import."); Files.Dereference(item.Files.Select(f => f.FileInfo).ToArray()); } } try { - LogForModel(item, "Beginning import..."); + LogForModel(item, @"Beginning import..."); item.Files = archive != null ? createFileInfos(archive, Files) : new List(); item.Hash = ComputeHash(item, archive); @@ -407,7 +407,7 @@ namespace osu.Game.Database { try { - if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}"); + if (!write.IsTransactionLeader) throw new InvalidOperationException(@$"Ensure there is no parent transaction so errors can correctly be handled by {this}"); if (!checkedExisting) existing = CheckForExisting(item); @@ -417,14 +417,14 @@ namespace osu.Game.Database if (CanReuseExisting(existing, item)) { Undelete(existing); - LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); + LogForModel(item, @$"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); // existing item will be used; rollback new import and exit early. rollback(); flushEvents(true); return existing; } - LogForModel(item, $"Found existing but failed re-use check."); + LogForModel(item, @"Found existing but failed re-use check."); Delete(existing); ModelStore.PurgeDeletable(s => s.ID == existing.ID); } @@ -441,12 +441,12 @@ namespace osu.Game.Database } } - LogForModel(item, "Import successfully completed!"); + LogForModel(item, @"Import successfully completed!"); } catch (Exception e) { if (!(e is TaskCanceledException)) - LogForModel(item, "Database import or population failed and has been rolled back.", e); + LogForModel(item, @"Database import or population failed and has been rolled back.", e); rollback(); flushEvents(false); @@ -466,7 +466,7 @@ namespace osu.Game.Database var retrievedItem = ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == item.ID); if (retrievedItem == null) - throw new ArgumentException("Specified model could not be found", nameof(item)); + throw new ArgumentException(@"Specified model could not be found", nameof(item)); using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) ExportModelTo(retrievedItem, outputStream); @@ -757,7 +757,7 @@ namespace osu.Game.Database { string fullPath = storage.GetFullPath(ImportFromStablePath); - Logger.Log($"Folder \"{fullPath}\" not available in the target osu!stable installation to import {HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error); + Logger.Log(@$"Folder ""{fullPath}"" not available in the target osu!stable installation to import {HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error); return Task.CompletedTask; } @@ -841,7 +841,7 @@ namespace osu.Game.Database private DbSet queryModel() => ContextFactory.Get().Set(); - protected virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace("Info", "").ToLower()}"; + protected virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; #region Event handling / delaying From 3d19364a7121702bb9b6c61068e82fb7223129af Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 28 Jun 2021 11:20:00 +0800 Subject: [PATCH 1122/1621] Use `BindValueChanged` instead of setting the value in property setter --- .../Overlays/Settings/SettingsNumberBox.cs | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index bc86f2c6dc..de3e5a6bb0 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -20,22 +20,18 @@ namespace osu.Game.Overlays.Settings { private readonly BindableWithCurrent current = new BindableWithCurrent(); - private readonly OutlinedNumberBox numberBox; - public Bindable Current { - get => current; - set - { - current.Current = value; - numberBox.Text = value.Value.ToString(); - } + get => current.Current; + set => current.Current = value; } public NumberControl() { AutoSizeAxes = Axes.Y; + OutlinedNumberBox numberBox; + InternalChildren = new[] { numberBox = new OutlinedNumberBox @@ -55,13 +51,11 @@ namespace osu.Game.Overlays.Settings current.Value = value; }); - } - protected override void Update() - { - base.Update(); - if (Current.Value == null) - numberBox.Current.Value = ""; + Current.BindValueChanged(e => + { + numberBox.Current.Value = e.NewValue?.ToString(); + }); } } } From ea8993d6d6363654490d210fcac4ba369ac7d3d0 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 28 Jun 2021 11:33:19 +0800 Subject: [PATCH 1123/1621] Use `IHasRepeats` instead of `IHasPathWithRepeats` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 1a6de87745..b2513e4983 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -445,7 +445,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (almostEquals(time, hitObject.StartTime)) return true; - if (!(hitObject is IHasPathWithRepeats s)) + if (!(hitObject is IHasRepeats s)) return false; if (!almostBigger(time, hitObject.StartTime) || !almostBigger(s.EndTime, time)) @@ -457,7 +457,7 @@ namespace osu.Game.Rulesets.Osu.Mods IList samples; - if (sampleObj is IHasPathWithRepeats slider) + if (sampleObj is IHasRepeats slider) samples = slider.NodeSamples[nodeIndexFromTime(slider, time - sampleObj.StartTime)]; else samples = sampleObj.Samples; @@ -471,7 +471,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// The slider. /// The time since the start time of the slider. /// Index of the node. -1 if there isn't a node at the specific time. - private int nodeIndexFromTime(IHasPathWithRepeats curve, double timeSinceStart) + private int nodeIndexFromTime(IHasRepeats curve, double timeSinceStart) { double spanDuration = curve.Duration / curve.SpanCount(); double nodeIndex = timeSinceStart / spanDuration; From a3946a1265fbee6968daf04dbf563b95e06330b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 14:07:21 +0900 Subject: [PATCH 1124/1621] Fix typo in newly added xmldoc Co-authored-by: Salman Ahmed --- osu.Game/Database/ArchiveModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 5446c3b851..3798c0c6ae 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -311,7 +311,7 @@ namespace osu.Game.Database /// /// Whether the implementation overrides with a custom implementation. - /// Custom has implementations must bypass the early exit in the import flow (see usage). + /// Custom hash implementations must bypass the early exit in the import flow (see usage). /// protected virtual bool HasCustomHashFunction => false; From e387feb1d6261b491246e014de199e921a290103 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 14:39:55 +0900 Subject: [PATCH 1125/1621] Add inline comment mentioning why `CreateChildDependencies` is being used in this instance --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index abf5cb040a..cb8b0fb3c8 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -47,11 +47,11 @@ namespace osu.Game.Skinning protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { parentSource = parent.Get(); - - UpdateSkinSources(); - parentSource.SourceChanged += OnSourceChanged; + // ensure sources are populated and ready for use before childrens' asynchronous load flow. + UpdateSkinSources(); + return base.CreateChildDependencies(parent); } From 73bd88cb318ae1c8b4690a8398587bb87f83b3ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 14:44:52 +0900 Subject: [PATCH 1126/1621] Simplify caching in test --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 0dde0a8194..25619de323 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -24,12 +24,8 @@ namespace osu.Game.Tests.Rulesets protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset(); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(new TestSkinProvider()); - return dependencies; - } + [Cached(typeof(ISkinSource))] + private readonly ISkinSource testSource = new TestSkinProvider(); [Test] public void TestEarlyAddedSkinRequester() From f777741ca788c3ea1a83830cd5db7286f943e67f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 14:51:27 +0900 Subject: [PATCH 1127/1621] Simplify instantiation --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 8a807eff21..a1db4ba6d1 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -96,12 +96,14 @@ namespace osu.Game.Skinning { int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + var rulesetResources = new ResourcesSkin(resources, host, audio); + if (defaultSkinIndex >= 0) - SkinSources.Insert(defaultSkinIndex, new ResourcesSkin(resources, host, audio)); + SkinSources.Insert(defaultSkinIndex, rulesetResources); else { // Tests may potentially override the SkinManager with another source that doesn't include it in AllSources. - SkinSources.Add(new ResourcesSkin(resources, host, audio)); + SkinSources.Add(rulesetResources); } } } From c281e43cd8beb91f50e0853579fab33699718502 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:04:14 +0900 Subject: [PATCH 1128/1621] Remove `Dispose()` special case and add explicit exception to make debugging issues non-deadlock --- 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 4d81f8676f..2f70d8af22 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -97,6 +97,9 @@ namespace osu.Game.Database { try { + if (IsDisposed) + throw new InvalidOperationException(@"Attempted to retrieve a context after the factor has already been disposed."); + blockingLock.Wait(); contexts_created.Value++; @@ -128,11 +131,8 @@ namespace osu.Game.Database { base.Dispose(isDisposing); - // In the standard case, operations will already be blocked by the Update thread "pausing" from GameHost exit. - // This avoids waiting (forever) on an already entered semaphore. - if (context != null || active_usages.Value > 0) - BlockAllOperations(); - + // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. + BlockAllOperations(); blockingLock?.Dispose(); } From 7dd566dc46639a28e86fda9bd0ba13030aaf74ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:08:49 +0900 Subject: [PATCH 1129/1621] Add null check for safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index dec738e5b3..bf1b449292 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -370,7 +370,7 @@ namespace osu.Game switch (state.NewValue) { case GameThreadState.Running: - blocking.Dispose(); + blocking?.Dispose(); blocking = null; break; From 692f24437ef5ec3afd2eed87143da90bd621c5fd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 09:11:28 +0300 Subject: [PATCH 1130/1621] Maintain ruleset resources skin across multiple source changes --- .../Skinning/RulesetSkinProvidingContainer.cs | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index a1db4ba6d1..076f8b03dc 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -46,21 +46,20 @@ namespace osu.Game.Skinning }; } - [Resolved] - private GameHost host { get; set; } - - [Resolved] - private AudioManager audio { get; set; } - [Resolved] private SkinManager skinManager { get; set; } [Resolved] private ISkinSource skinSource { get; set; } + private ResourcesSkin rulesetResourcesSkin; + [BackgroundDependencyLoader] - private void load() + private void load(GameHost host, AudioManager audio) { + if (Ruleset.CreateResourceStore() is IResourceStore resources) + rulesetResourcesSkin = new ResourcesSkin(resources, host, audio); + UpdateSkins(); skinSource.SourceChanged += OnSourceChanged; } @@ -73,9 +72,6 @@ namespace osu.Game.Skinning protected virtual void UpdateSkins() { - foreach (var resourcesSkin in SkinSources.OfType()) - resourcesSkin.Dispose(); - SkinSources.Clear(); foreach (var skin in skinSource.AllSources) @@ -92,20 +88,12 @@ namespace osu.Game.Skinning } } - if (Ruleset.CreateResourceStore() is IResourceStore resources) - { - int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); - var rulesetResources = new ResourcesSkin(resources, host, audio); - - if (defaultSkinIndex >= 0) - SkinSources.Insert(defaultSkinIndex, rulesetResources); - else - { - // Tests may potentially override the SkinManager with another source that doesn't include it in AllSources. - SkinSources.Add(rulesetResources); - } - } + if (defaultSkinIndex >= 0) + SkinSources.Insert(defaultSkinIndex, rulesetResourcesSkin); + else + SkinSources.Add(rulesetResourcesSkin); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) @@ -126,6 +114,8 @@ namespace osu.Game.Skinning if (skinSource != null) skinSource.SourceChanged -= OnSourceChanged; + + rulesetResourcesSkin?.Dispose(); } } } From f598de4cdb0069109abc42485349b2026ea323f8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 09:18:29 +0300 Subject: [PATCH 1131/1621] ResourcesSkin -> ResourceStoreBackedSkin --- .../{ResourcesSkin.cs => ResourceStoreBackedSkin.cs} | 4 ++-- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) rename osu.Game/Skinning/{ResourcesSkin.cs => ResourceStoreBackedSkin.cs} (91%) diff --git a/osu.Game/Skinning/ResourcesSkin.cs b/osu.Game/Skinning/ResourceStoreBackedSkin.cs similarity index 91% rename from osu.Game/Skinning/ResourcesSkin.cs rename to osu.Game/Skinning/ResourceStoreBackedSkin.cs index 90020495c3..f041b82cf4 100644 --- a/osu.Game/Skinning/ResourcesSkin.cs +++ b/osu.Game/Skinning/ResourceStoreBackedSkin.cs @@ -19,12 +19,12 @@ namespace osu.Game.Skinning /// /// An that uses an underlying with namespaces for resources retrieval. /// - public class ResourcesSkin : ISkin, IDisposable + public class ResourceStoreBackedSkin : ISkin, IDisposable { private readonly TextureStore textures; private readonly ISampleStore samples; - public ResourcesSkin(IResourceStore resources, GameHost host, AudioManager audio) + public ResourceStoreBackedSkin(IResourceStore resources, GameHost host, AudioManager audio) { textures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); samples = audio.GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 076f8b03dc..d35e9c1ff3 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.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.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -52,13 +51,13 @@ namespace osu.Game.Skinning [Resolved] private ISkinSource skinSource { get; set; } - private ResourcesSkin rulesetResourcesSkin; + private ResourceStoreBackedSkin rulesetResourcesSkin; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { if (Ruleset.CreateResourceStore() is IResourceStore resources) - rulesetResourcesSkin = new ResourcesSkin(resources, host, audio); + rulesetResourcesSkin = new ResourceStoreBackedSkin(resources, host, audio); UpdateSkins(); skinSource.SourceChanged += OnSourceChanged; From 88c6143aaeedc656e9fe81d2ea2efa7ce8069209 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:23:19 +0900 Subject: [PATCH 1132/1621] Rename variables in line with standards --- osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs | 6 +++--- osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs | 6 +++--- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index e91eed420e..27ad6650d1 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -221,11 +221,11 @@ namespace osu.Game.Tournament.Screens.Editors private void load(RulesetStore rulesets) { beatmapId.Value = Model.ID; - beatmapId.BindValueChanged(idInt => + beatmapId.BindValueChanged(id => { - Model.ID = idInt.NewValue ?? 0; + Model.ID = id.NewValue ?? 0; - if (idInt.NewValue != idInt.OldValue) + if (id.NewValue != id.OldValue) Model.BeatmapInfo = null; if (Model.BeatmapInfo != null) diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index 4b50d74dc8..6418bf97da 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -229,11 +229,11 @@ namespace osu.Game.Tournament.Screens.Editors private void load(RulesetStore rulesets) { beatmapId.Value = Model.ID; - beatmapId.BindValueChanged(idInt => + beatmapId.BindValueChanged(id => { - Model.ID = idInt.NewValue ?? 0; + Model.ID = id.NewValue ?? 0; - if (idInt.NewValue != idInt.OldValue) + if (id.NewValue != id.OldValue) Model.BeatmapInfo = null; if (Model.BeatmapInfo != null) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 22ec2d3398..0d2e64f300 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -279,11 +279,11 @@ namespace osu.Game.Tournament.Screens.Editors private void load() { userId.Value = user.Id; - userId.BindValueChanged(idInt => + userId.BindValueChanged(id => { - user.Id = idInt.NewValue ?? 0; + user.Id = id.NewValue ?? 0; - if (idInt.NewValue != idInt.OldValue) + if (id.NewValue != id.OldValue) user.Username = string.Empty; if (!string.IsNullOrEmpty(user.Username)) From eeb56970ef036f6dedebe72e57d218410d5b7b88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:24:55 +0900 Subject: [PATCH 1133/1621] Make `OutlinedNumberBox` private and nested again --- osu.Game/Overlays/Settings/OutlinedNumberBox.cs | 10 ---------- osu.Game/Overlays/Settings/SettingsNumberBox.cs | 5 +++++ 2 files changed, 5 insertions(+), 10 deletions(-) delete mode 100644 osu.Game/Overlays/Settings/OutlinedNumberBox.cs diff --git a/osu.Game/Overlays/Settings/OutlinedNumberBox.cs b/osu.Game/Overlays/Settings/OutlinedNumberBox.cs deleted file mode 100644 index 6fcadc09e1..0000000000 --- a/osu.Game/Overlays/Settings/OutlinedNumberBox.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.Overlays.Settings -{ - public class OutlinedNumberBox : OutlinedTextBox - { - protected override bool CanAddCharacter(char character) => char.IsNumber(character); - } -} diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index de3e5a6bb0..2fbe522479 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -58,5 +58,10 @@ namespace osu.Game.Overlays.Settings }); } } + + private class OutlinedNumberBox : OutlinedTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } } } From e4780d4b06f9eb7b2b420114853f9b99b93481c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:29:47 +0900 Subject: [PATCH 1134/1621] 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 3c4380e355..c845d7f276 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 f91620bd25..f047859dbb 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 22c4340ba2..304047ad12 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 779a1b322ca177632e6ba76e50cb431c40cf2600 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 09:38:42 +0300 Subject: [PATCH 1135/1621] Add comment explaining insertion of ruleset skin before default skin Co-authored-by: Dean Herbert --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index d35e9c1ff3..5f0ca27170 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -89,6 +89,9 @@ namespace osu.Game.Skinning int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + // Ruleset resources should be given the ability to override game-wide defaults + // This is achieved by placing them before an instance of DefaultSkin. + // Note that DefaultSkin may not be present in some test scenes. if (defaultSkinIndex >= 0) SkinSources.Insert(defaultSkinIndex, rulesetResourcesSkin); else From 842f033522efc6b90ee88ff8d8684897e09d09c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 16:11:55 +0900 Subject: [PATCH 1136/1621] Remove no longer necessary exception --- osu.Game/Database/RealmContextFactory.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 2f70d8af22..461444870e 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -44,9 +44,6 @@ 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(); From 90f0bc87f5002b0479ec01508584c637d042358f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 16:12:21 +0900 Subject: [PATCH 1137/1621] Add safety against double disposal --- osu.Game/Database/RealmContextFactory.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 461444870e..0b0263a16b 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -126,22 +126,28 @@ namespace osu.Game.Database protected override void Dispose(bool isDisposing) { - base.Dispose(isDisposing); + if (!IsDisposed) + { + // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. + BlockAllOperations(); + blockingLock?.Dispose(); + } - // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. - BlockAllOperations(); - blockingLock?.Dispose(); + base.Dispose(isDisposing); } public IDisposable BlockAllOperations() { + if (IsDisposed) + throw new InvalidOperationException(@"Attempted to block operations after already disposed."); + blockingLock.Wait(); flushContexts(); return new InvokeOnDisposal(this, endBlockingSection); - } - private static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); + static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); + } private void flushContexts() { From f78cedd0e1d799aaec1562bcb1d1677c72bb5536 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 16:14:14 +0900 Subject: [PATCH 1138/1621] Reorder methods and add xmldoc for `BlockAllOperations` --- osu.Game/Database/RealmContextFactory.cs | 65 ++++++++++++++---------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 0b0263a16b..6214f4ca81 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -76,10 +76,26 @@ namespace osu.Game.Database return new RealmWriteUsage(createContext(), writeComplete); } - private void writeComplete() + /// + /// Flush any active contexts and block any further writes. + /// + /// + /// This should be used in places we need to ensure no ongoing reads/writes are occurring with realm. + /// ie. to move the realm backing file to a new location. + /// + /// An which should be disposed to end the blocking section. + /// Thrown if this context is already disposed. + public IDisposable BlockAllOperations() { - Monitor.Exit(writeLock); - pending_writes.Value--; + if (IsDisposed) + throw new InvalidOperationException(@"Attempted to block operations after already disposed."); + + blockingLock.Wait(); + flushContexts(); + + return new InvokeOnDisposal(this, endBlockingSection); + + static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); } protected override void Update() @@ -113,6 +129,12 @@ namespace osu.Game.Database } } + private void writeComplete() + { + Monitor.Exit(writeLock); + pending_writes.Value--; + } + private void onMigration(Migration migration, ulong lastSchemaVersion) { switch (lastSchemaVersion) @@ -124,31 +146,6 @@ namespace osu.Game.Database } } - protected override void Dispose(bool isDisposing) - { - if (!IsDisposed) - { - // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. - BlockAllOperations(); - blockingLock?.Dispose(); - } - - base.Dispose(isDisposing); - } - - public IDisposable BlockAllOperations() - { - if (IsDisposed) - throw new InvalidOperationException(@"Attempted to block operations after already disposed."); - - blockingLock.Wait(); - flushContexts(); - - return new InvokeOnDisposal(this, endBlockingSection); - - static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); - } - private void flushContexts() { var previousContext = context; @@ -161,6 +158,18 @@ namespace osu.Game.Database previousContext?.Dispose(); } + protected override void Dispose(bool isDisposing) + { + if (!IsDisposed) + { + // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. + BlockAllOperations(); + blockingLock?.Dispose(); + } + + base.Dispose(isDisposing); + } + /// /// A usage of realm from an arbitrary thread. /// From d4ea73d727ec94ed118e3279bc8752758b9a73bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 16:17:09 +0900 Subject: [PATCH 1139/1621] Simplify disposal exceptions --- osu.Game/Database/RealmContextFactory.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 6214f4ca81..71617b258d 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -84,11 +84,10 @@ namespace osu.Game.Database /// ie. to move the realm backing file to a new location. /// /// An which should be disposed to end the blocking section. - /// Thrown if this context is already disposed. public IDisposable BlockAllOperations() { if (IsDisposed) - throw new InvalidOperationException(@"Attempted to block operations after already disposed."); + throw new ObjectDisposedException(nameof(RealmContextFactory)); blockingLock.Wait(); flushContexts(); @@ -111,7 +110,7 @@ namespace osu.Game.Database try { if (IsDisposed) - throw new InvalidOperationException(@"Attempted to retrieve a context after the factor has already been disposed."); + throw new ObjectDisposedException(nameof(RealmContextFactory)); blockingLock.Wait(); From 035fe2ad4919e67be14d4fc2323d0daa10753fa2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 11:29:43 +0300 Subject: [PATCH 1140/1621] Mark ruleset skin provider test scene as headless --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index e060c8578a..28ad7ed6a7 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Rulesets; using osu.Game.Skinning; @@ -18,6 +19,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Rulesets { + [HeadlessTest] public class TestSceneRulesetSkinProvidingContainer : OsuTestScene { private SkinRequester requester; From b5c3c9d55099a47d775d51ce5e64ed4d554a3244 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 28 Jun 2021 17:37:58 +0900 Subject: [PATCH 1141/1621] Disable realm analytics submission --- FodyWeavers.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FodyWeavers.xml b/FodyWeavers.xml index cc07b89533..ea490e3297 100644 --- a/FodyWeavers.xml +++ b/FodyWeavers.xml @@ -1,3 +1,3 @@  - + \ No newline at end of file From 7197998a104d0e25db060cf6adbbb4f14870bf64 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 12:43:12 +0300 Subject: [PATCH 1142/1621] Remove resolution to `SkinManager` and use pattern matching instead --- osu.Game/Skinning/RulesetSkinProvidingContainer.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index c3d01218d7..26646d87fe 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -45,15 +46,12 @@ namespace osu.Game.Skinning }; } - private SkinManager skinManager; private ISkinSource parentSource; private ResourceStoreBackedSkin rulesetResourcesSkin; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - skinManager = parent.Get(); - parentSource = parent.Get(); parentSource.SourceChanged += OnSourceChanged; @@ -90,13 +88,13 @@ namespace osu.Game.Skinning } } - int defaultSkinIndex = SkinSources.IndexOf(skinManager.DefaultSkin); + int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType().Last()); // Ruleset resources should be given the ability to override game-wide defaults - // This is achieved by placing them before an instance of DefaultSkin. + // This is achieved by placing them before the last instance of DefaultSkin. // Note that DefaultSkin may not be present in some test scenes. - if (defaultSkinIndex >= 0) - SkinSources.Insert(defaultSkinIndex, rulesetResourcesSkin); + if (lastDefaultSkinIndex >= 0) + SkinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); else SkinSources.Add(rulesetResourcesSkin); } From 66fc95c1118f74fa9ce78d154862b375428afa9e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 12:43:55 +0300 Subject: [PATCH 1143/1621] Use `LastOrDefault` instead --- 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 26646d87fe..19efc66814 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -88,7 +88,7 @@ namespace osu.Game.Skinning } } - int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType().Last()); + int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType().LastOrDefault()); // Ruleset resources should be given the ability to override game-wide defaults // This is achieved by placing them before the last instance of DefaultSkin. From ab977d91f0458c406c3b44db1fe21c664d3c4721 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 28 Jun 2021 20:40:22 +0700 Subject: [PATCH 1144/1621] fix image test not load looks like forgot to change this after `CurrentPath` shenanigan --- .../Online/TestSceneWikiMarkdownContainer.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index b6dce2c398..ec43ee2be3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -102,7 +102,7 @@ needs_cleanup: true { AddStep("Add absolute image", () => { - markdownContainer.DocumentUrl = "https://dev.ppy.sh"; + markdownContainer.CurrentPath = "https://dev.ppy.sh"; markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)"; }); } @@ -112,8 +112,7 @@ needs_cleanup: true { AddStep("Add relative image", () => { - markdownContainer.DocumentUrl = "https://dev.ppy.sh"; - markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Interface/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/"; markdownContainer.Text = "![intro](img/intro-screen.jpg)"; }); } @@ -123,8 +122,7 @@ needs_cleanup: true { AddStep("Add paragraph with block image", () => { - markdownContainer.DocumentUrl = "https://dev.ppy.sh"; - markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Interface/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/"; markdownContainer.Text = @"Line before image ![play menu](img/play-menu.jpg ""Main Menu in osu!"") @@ -138,7 +136,7 @@ Line after image"; { AddStep("Add inline image", () => { - markdownContainer.DocumentUrl = "https://dev.ppy.sh"; + markdownContainer.CurrentPath = "https://dev.ppy.sh"; markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!"; }); } @@ -148,7 +146,7 @@ Line after image"; { AddStep("Add Table", () => { - markdownContainer.DocumentUrl = "https://dev.ppy.sh"; + markdownContainer.CurrentPath = "https://dev.ppy.sh"; markdownContainer.Text = @" | Image | Name | Effect | | :-: | :-: | :-- | @@ -166,11 +164,6 @@ Line after image"; { public LinkInline Link; - public new string DocumentUrl - { - set => base.DocumentUrl = value; - } - public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer { UrlAdded = link => Link = link, From 0e29ae037b8773ce4c88318ca6063998f2979c30 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 28 Jun 2021 20:50:29 +0700 Subject: [PATCH 1145/1621] add wide image test --- .../Online/TestSceneWikiMarkdownContainer.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index ec43ee2be3..69caa2896b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -160,6 +160,20 @@ Line after image"; }); } + [Test] + public void TestWideImageNotExceedContainer() + { + AddStep("Add image", () => + { + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/osu!_Program_Files/"; + markdownContainer.Text = "![](img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")"; + }); + AddStep("Change container width", () => + { + markdownContainer.Width = 0.5f; + }); + } + private class TestMarkdownContainer : WikiMarkdownContainer { public LinkInline Link; From 1c074ff0182c90f56b370d37f7511c942bedeaea Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 28 Jun 2021 20:55:40 +0700 Subject: [PATCH 1146/1621] add `BlockMarkdownImage` change from AutoSize Both to AutoSize Y and RelativeSize X --- .../Wiki/Markdown/WikiMarkdownImageBlock.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs index 179762103a..fbc0b8ee67 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs @@ -32,11 +32,7 @@ namespace osu.Game.Overlays.Wiki.Markdown { Children = new Drawable[] { - new WikiMarkdownImage(linkInline) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, + new BlockMarkdownImage(linkInline), parentTextComponent.CreateSpriteText().With(t => { t.Text = linkInline.Title; @@ -45,5 +41,15 @@ namespace osu.Game.Overlays.Wiki.Markdown }), }; } + + private class BlockMarkdownImage : WikiMarkdownImage + { + public BlockMarkdownImage(LinkInline linkInline) + : base(linkInline) + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + } + } } } From 2b8d41c0452e94cb9577f64771e08e9b18161f0c Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 28 Jun 2021 21:04:56 +0700 Subject: [PATCH 1147/1621] add `BlockImageContainer` change from AutoSize Both to AutoSize Y and RelativeSize X --- .../Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs index fbc0b8ee67..b321cf328e 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs @@ -50,6 +50,18 @@ namespace osu.Game.Overlays.Wiki.Markdown AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; } + + protected override ImageContainer CreateImageContainer(string url) => new BlockImageContainer(url); + + private class BlockImageContainer : ImageContainer + { + public BlockImageContainer(string url) + : base(url) + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + } + } } } } From e58f690210037ed9ddcfa3c3651fc3910442f7fa Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 28 Jun 2021 21:09:00 +0700 Subject: [PATCH 1148/1621] centering image sprite --- osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs index b321cf328e..8d2eca9d5d 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Sprites; using osuTK; namespace osu.Game.Overlays.Wiki.Markdown @@ -61,6 +62,12 @@ namespace osu.Game.Overlays.Wiki.Markdown AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; } + + protected override Sprite CreateSpriteImage() => base.CreateSpriteImage().With(s => + { + s.Anchor = Anchor.TopCentre; + s.Origin = Anchor.TopCentre; + }); } } } From e4ca6a4266ce8624e189e9409128b97bd02b44bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 01:55:09 +0900 Subject: [PATCH 1149/1621] Serialise and send ruleset ID as part of score submission --- osu.Game/Scoring/ScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 3944c1d3de..4fd1d00fef 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -46,7 +46,7 @@ namespace osu.Game.Scoring [JsonIgnore] public int Combo { get; set; } // Todo: Shouldn't exist in here - [JsonIgnore] + [JsonProperty("ruleset_id")] public int RulesetID { get; set; } [JsonProperty("passed")] From 006cc331c8991e28fefae250824249bf3f0d569b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 18:51:43 +0300 Subject: [PATCH 1150/1621] Separate `IMutateApproachCircles` to requiring and hiding --- ...teApproachCircles.cs => IHidesApproachCircles.cs} | 4 ++-- .../Mods/IRequiresApproachCircles.cs | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/Mods/{IMutateApproachCircles.cs => IHidesApproachCircles.cs} (57%) create mode 100644 osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs diff --git a/osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs similarity index 57% rename from osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs rename to osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs index 60a5825241..07ca93126d 100644 --- a/osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs +++ b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs @@ -4,9 +4,9 @@ namespace osu.Game.Rulesets.Osu.Mods { /// - /// Any mod which affects the animation or visibility of approach circles. Should be used for incompatibility purposes. + /// Any mod which completely hides the approach circles. Used for incompatibility with . /// - public interface IMutateApproachCircles + public interface IHidesApproachCircles { } } diff --git a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs new file mode 100644 index 0000000000..f2bd836835 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.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 requires the approach circles to be visible. Used for incompatibility with . + /// + public interface IRequiresApproachCircles + { + } +} From 6a67a6736a5c248efbeab3e2b151838069950038 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 18:52:39 +0300 Subject: [PATCH 1151/1621] Mark `IRequireApproachCircles` mods as incompatible with `IHidesApproachCircles` --- osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index 526e29ad53..d832411104 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject, IMutateApproachCircles + public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject, IRequiresApproachCircles { public override string Name => "Approach Different"; public override string Acronym => "AD"; @@ -20,7 +20,7 @@ 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) }; + public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) }; [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/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 84263221a7..a05e4dea03 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, IMutateApproachCircles + public class OsuModTraceable : ModWithVisibilityAdjustment, IRequiresApproachCircles { 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(IMutateApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { From baf736026b1e58578ee10f3fb269ade776a3c35e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 18:54:21 +0300 Subject: [PATCH 1152/1621] Mark `IHidesApproachCircles` mods as incompatible with `IRequiresApproachCircles` --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 16b38cd0b1..ee591465cd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -15,12 +15,12 @@ using osu.Game.Rulesets.Osu.Skinning; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModHidden : ModHidden, IMutateApproachCircles + public class OsuModHidden : ModHidden, IHidesApproachCircles { 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(IMutateApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles) }; 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 6dfabed0df..9aafb02fa2 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, IMutateApproachCircles + public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment, IHidesApproachCircles { 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(IMutateApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles) }; 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 d3ca2973f0..1854c20aef 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, IMutateApproachCircles + public class OsuModSpinIn : ModWithVisibilityAdjustment, IHidesApproachCircles { 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(IMutateApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles) }; private const int rotate_offset = 360; private const float rotate_starting_width = 2; From 69dac018c7c7b7a62dc3aeb08fa08e4956c39051 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Jun 2021 18:55:24 +0300 Subject: [PATCH 1153/1621] Make `OsuModSpinIn` as incompatible with other `IHidesApproachCircles` mods Respecting the TODO comment above it, mark `OsuModSpinIn` as incompatible with other `IHidesApproachCircles` mods as well. --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index ee591465cd..9c7784a00a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods 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(IRequiresApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) }; 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 9aafb02fa2..778447e444 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected virtual float EndScale => 1; - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) }; 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 1854c20aef..47a771c2f1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -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(IRequiresApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(IHidesApproachCircles) }; private const int rotate_offset = 360; private const float rotate_starting_width = 2; From d88d2644918bba27389c2e6ea5308ab965c2f736 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 29 Jun 2021 09:49:45 +0700 Subject: [PATCH 1154/1621] prevent image sprite exceed its parent width --- .../Wiki/Markdown/WikiMarkdownImageBlock.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs index 8d2eca9d5d..4b0490d034 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs @@ -63,11 +63,28 @@ namespace osu.Game.Overlays.Wiki.Markdown RelativeSizeAxes = Axes.X; } - protected override Sprite CreateSpriteImage() => base.CreateSpriteImage().With(s => + protected override Sprite CreateSpriteImage() => new ImageSprite(); + + private class ImageSprite : Sprite { - s.Anchor = Anchor.TopCentre; - s.Origin = Anchor.TopCentre; - }); + public ImageSprite() + { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + } + + protected override void Update() + { + base.Update(); + + if (Width > Parent.DrawWidth) + { + float ratio = Height / Width; + Width = Parent.DrawWidth; + Height = ratio * Width; + } + } + } } } } From a1fe9df378d88cd5edca44331fb867c59f35d94d Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 29 Jun 2021 10:14:51 +0700 Subject: [PATCH 1155/1621] add test to check sprite image width --- .../Visual/Online/TestSceneWikiMarkdownContainer.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 69caa2896b..af2e4fc91a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using Markdig.Syntax.Inlines; using NUnit.Framework; using osu.Framework.Allocation; @@ -9,6 +10,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Graphics.Containers.Markdown; using osu.Game.Overlays; using osu.Game.Overlays.Wiki.Markdown; @@ -168,10 +172,19 @@ Line after image"; markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/osu!_Program_Files/"; markdownContainer.Text = "![](img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")"; }); + + AddUntilStep("Wait image to load", () => markdownContainer.ChildrenOfType().First().DelayedLoadCompleted); + AddStep("Change container width", () => { markdownContainer.Width = 0.5f; }); + + AddAssert("Image not exceed container width", () => + { + var spriteImage = markdownContainer.ChildrenOfType().First(); + return Precision.DefinitelyBigger(markdownContainer.DrawWidth, spriteImage.DrawWidth); + }); } private class TestMarkdownContainer : WikiMarkdownContainer From 96e09605d8fabe836396d2cb92d0deabaacb93a2 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 29 Jun 2021 12:33:40 +0800 Subject: [PATCH 1156/1621] Osu random mod improvements - Reduce "jump streams" by increasing maximum jump angle and variance in jump angle - Reduce weird jumps to sliders by shifting hit circles in front of sliders --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 66 ++++++++++++++++++++-- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index d1212096bf..d3a7f4fc74 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -26,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.Mods private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; + /// + /// Number of previous hit circles to be shifted together when a slider needs to be moved. + /// + private const int shift_object_count = 10; + private Random rng; public void ApplyToBeatmap(IBeatmap beatmap) @@ -43,14 +48,22 @@ namespace osu.Game.Rulesets.Osu.Mods float rateOfChangeMultiplier = 0; + int cntSinceNewCombo = 0; + for (int i = 0; i < hitObjects.Count; i++) { var hitObject = hitObjects[i]; var current = new RandomObjectInfo(hitObject); - // rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams - if (i % 3 == 0) + // rateOfChangeMultiplier only changes every 5 iterations in a combo + // to prevent shaky-line-shaped streams + if (hitObject.NewCombo) + cntSinceNewCombo = 0; + else + cntSinceNewCombo++; + + if (cntSinceNewCombo % 5 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; if (hitObject is Spinner) @@ -67,7 +80,24 @@ namespace osu.Game.Rulesets.Osu.Mods current.EndPositionRandomised = current.PositionRandomised; if (hitObject is Slider slider) - moveSliderIntoPlayfield(slider, current); + { + Vector2 shift = moveSliderIntoPlayfield(slider, current); + + if (shift != Vector2.Zero) + { + var toBeShifted = new List(); + + for (int j = i - 1; j >= i - shift_object_count && j >= 0; j--) + { + if (!(hitObjects[j] is HitCircle)) break; + + toBeShifted.Add(hitObjects[j]); + } + + if (toBeShifted.Count > 0) + applyDecreasingShift(toBeShifted, shift); + } + } previous = current; } @@ -94,7 +124,9 @@ namespace osu.Game.Rulesets.Osu.Mods // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) // is proportional to the distance between the last and the current hit object // to allow jumps and prevent too sharp turns during streams. - var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * distanceToPrev / playfield_diagonal; + + // Allow maximum jump angle when jump distance is more than half of playfield diagonal length + var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * Math.Min(1f, distanceToPrev / (playfield_diagonal * 0.5f)); current.AngleRad = (float)randomAngleRad + previous.AngleRad; if (current.AngleRad < 0) @@ -122,10 +154,13 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Moves the and all necessary nested s into the if they aren't already. /// - private void moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) + /// The that this slider has been shifted by. + private Vector2 moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) { var minMargin = getMinSliderMargin(slider); + var prevPosition = slider.Position; + slider.Position = new Vector2( Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right), Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom) @@ -135,6 +170,27 @@ namespace osu.Game.Rulesets.Osu.Mods currentObjectInfo.EndPositionRandomised = slider.EndPosition; shiftNestedObjects(slider, currentObjectInfo.PositionRandomised - currentObjectInfo.PositionOriginal); + + return slider.Position - prevPosition; + } + + /// + /// Decreasingly shift a list of s by a specified amount. + /// The first item in the list is shifted by the largest amount, while the last item is shifted by the smallest amount. + /// + /// The list of hit objects to be shifted. + /// The amount to be shifted. + private void applyDecreasingShift(IList hitObjects, Vector2 shift) + { + for (int i = 0; i < hitObjects.Count; i++) + { + Vector2 position = hitObjects[i].Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); + + position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X); + position.Y = MathHelper.Clamp(position.Y, 0, OsuPlayfield.BASE_SIZE.Y); + + hitObjects[i].Position = position; + } } /// From d4ff4b26f55f7532fc2a67751a5f46e976f8cd15 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 29 Jun 2021 12:49:25 +0800 Subject: [PATCH 1157/1621] Split part of `addHitSamples` to a subroutine --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 39 +++++++++------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index b2513e4983..3ad432a40b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -227,34 +227,13 @@ namespace osu.Game.Rulesets.Osu.Mods private void addHitSamples(IEnumerable hitObjects) { - var lastSampleIdx = 0; - foreach (var obj in hitObjects) { var samples = getSamplesAtTime(origHitObjects, obj.StartTime); - if (samples == null) - { - // If samples aren't available at the exact start time of the object, - // use samples (without additions) in the closest original hit object instead - - while (lastSampleIdx < origHitObjects.Count && origHitObjects[lastSampleIdx].StartTime <= obj.StartTime) - lastSampleIdx++; - - if (lastSampleIdx >= origHitObjects.Count) continue; - - if (lastSampleIdx > 0) - { - // Get samples from the previous hit object if it is closer in time - if (obj.StartTime - origHitObjects[lastSampleIdx - 1].StartTime < origHitObjects[lastSampleIdx].StartTime - obj.StartTime) - lastSampleIdx--; - } - - // Remove additions - obj.Samples = origHitObjects[lastSampleIdx].Samples.Where(s => !HitSampleInfo.AllAdditions.Contains(s.Name)).ToList(); - } - else - obj.Samples = samples; + // If samples aren't available at the exact start time of the object, + // use samples (without additions) in the closest original hit object instead + obj.Samples = samples ?? getClosestHitObject(origHitObjects, obj.StartTime).Samples.Where(s => !HitSampleInfo.AllAdditions.Contains(s.Name)).ToList(); } } @@ -426,6 +405,18 @@ namespace osu.Game.Rulesets.Osu.Mods return beats; } + private OsuHitObject getClosestHitObject(List hitObjects, double time) + { + var closestIdx = hitObjects.FindLastIndex(h => h.StartTime < time); + + if (closestIdx == hitObjects.Count - 1) return hitObjects[closestIdx]; + + // return the closest preceding/succeeding hit object, whoever is closer in time + return hitObjects[closestIdx + 1].StartTime - time < time - hitObjects[closestIdx].StartTime + ? hitObjects[closestIdx + 1] + : hitObjects[closestIdx]; + } + /// /// Get samples (if any) for a specific point in time. /// From 0c5777c2c82b4cd0cc1e7bbed59833c3e0d81a63 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 29 Jun 2021 12:56:05 +0800 Subject: [PATCH 1158/1621] Added comments --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index d3a7f4fc74..25bdac7be3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -89,6 +89,7 @@ namespace osu.Game.Rulesets.Osu.Mods for (int j = i - 1; j >= i - shift_object_count && j >= 0; j--) { + // only shift hit circles if (!(hitObjects[j] is HitCircle)) break; toBeShifted.Add(hitObjects[j]); @@ -184,6 +185,8 @@ namespace osu.Game.Rulesets.Osu.Mods { for (int i = 0; i < hitObjects.Count; i++) { + // The first object is shifted by a vector slightly smaller than shift + // The last object is shifted by a vector slightly larger than zero Vector2 position = hitObjects[i].Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X); From 2722565204b59c3b99be66ea801127863e28b3e8 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 29 Jun 2021 13:36:30 +0800 Subject: [PATCH 1159/1621] Take circle radius into account when clamping to playfield --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 36 ++++++++++++++-------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 25bdac7be3..62ca5e5fb4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -74,6 +74,10 @@ namespace osu.Game.Rulesets.Osu.Mods applyRandomisation(rateOfChangeMultiplier, previous, current); + // Move hit objects back into the playfield if they are outside of it, + // which would sometimes happen during big jumps otherwise. + current.PositionRandomised = clampToPlayfield(current.PositionRandomised, (float)hitObject.Radius); + hitObject.Position = current.PositionRandomised; // update end position as it may have changed as a result of the position update. @@ -142,14 +146,7 @@ namespace osu.Game.Rulesets.Osu.Mods current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); - var position = previous.EndPositionRandomised + posRelativeToPrev; - - // Move hit objects back into the playfield if they are outside of it, - // which would sometimes happen during big jumps otherwise. - position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X); - position.Y = MathHelper.Clamp(position.Y, 0, OsuPlayfield.BASE_SIZE.Y); - - current.PositionRandomised = position; + current.PositionRandomised = previous.EndPositionRandomised + posRelativeToPrev; } /// @@ -185,14 +182,12 @@ namespace osu.Game.Rulesets.Osu.Mods { for (int i = 0; i < hitObjects.Count; i++) { + var hitObject = hitObjects[i]; // The first object is shifted by a vector slightly smaller than shift // The last object is shifted by a vector slightly larger than zero - Vector2 position = hitObjects[i].Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); + Vector2 position = hitObject.Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); - position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X); - position.Y = MathHelper.Clamp(position.Y, 0, OsuPlayfield.BASE_SIZE.Y); - - hitObjects[i].Position = position; + hitObject.Position = clampToPlayfield(position, (float)hitObject.Radius); } } @@ -217,6 +212,13 @@ namespace osu.Game.Rulesets.Osu.Mods minMargin.Left = Math.Min(minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right); minMargin.Top = Math.Min(minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); + var radius = (float)slider.Radius; + + minMargin.Left += radius; + minMargin.Right += radius; + minMargin.Top += radius; + minMargin.Bottom += radius; + return minMargin; } @@ -236,6 +238,14 @@ namespace osu.Game.Rulesets.Osu.Mods } } + private Vector2 clampToPlayfield(Vector2 position, float radius) + { + position.X = MathHelper.Clamp(position.X, radius, OsuPlayfield.BASE_SIZE.X - radius); + position.Y = MathHelper.Clamp(position.Y, radius, OsuPlayfield.BASE_SIZE.Y - radius); + + return position; + } + private class RandomObjectInfo { public float AngleRad { get; set; } From a3b1e1d5fc477f54de3e359f96ea4f3ab5a90cda Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 15:18:40 +0900 Subject: [PATCH 1160/1621] Check for null ruleset in FilterCriteria --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 8e59dc8579..5f135a3e90 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { bool matchingFilter = true; - matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset)); + matchingFilter &= r.Room.Playlist.Count == 0 || criteria.Ruleset == null || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset)); if (!string.IsNullOrEmpty(criteria.SearchString)) matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); From 8d1eae7c705c95f159304b262f658348ce2c84c8 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 29 Jun 2021 14:25:45 +0800 Subject: [PATCH 1161/1621] Use `IndexInCurrentCombo` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 62ca5e5fb4..78a49f8e91 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Osu.Mods float rateOfChangeMultiplier = 0; - int cntSinceNewCombo = 0; - for (int i = 0; i < hitObjects.Count; i++) { var hitObject = hitObjects[i]; @@ -58,12 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods // rateOfChangeMultiplier only changes every 5 iterations in a combo // to prevent shaky-line-shaped streams - if (hitObject.NewCombo) - cntSinceNewCombo = 0; - else - cntSinceNewCombo++; - - if (cntSinceNewCombo % 5 == 0) + if (hitObject.IndexInCurrentCombo % 5 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; if (hitObject is Spinner) From 06beeee4d8004e6d7608761f3de0ab73a5b9ee07 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 15:39:01 +0900 Subject: [PATCH 1162/1621] Cleanup match header test --- .../Multiplayer/TestSceneMatchHeader.cs | 66 +++++++++---------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 3557bd9127..71ba5db481 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -17,43 +17,39 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public new void Setup() => Schedule(() => { - SelectedRoom.Value = new Room(); + SelectedRoom.Value = new Room + { + Name = { Value = "A very awesome room" }, + Host = { Value = new User { Id = 2, Username = "peppy" } }, + Playlist = + { + new PlaylistItem + { + Beatmap = + { + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Title = "Title", + Artist = "Artist", + AuthorString = "Author", + }, + Version = "Version", + Ruleset = new OsuRuleset().RulesetInfo + } + }, + RequiredMods = + { + new OsuModDoubleTime(), + new OsuModNoFail(), + new OsuModRelax(), + } + } + } + }; Child = new Header(); }); - - [Test] - public void TestBasicRoom() - { - AddStep("set basic room", () => - { - SelectedRoom.Value.Playlist.Add(new PlaylistItem - { - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Title = "Title", - Artist = "Artist", - AuthorString = "Author", - }, - Version = "Version", - Ruleset = new OsuRuleset().RulesetInfo - } - }, - RequiredMods = - { - new OsuModDoubleTime(), - new OsuModNoFail(), - new OsuModRelax(), - } - }); - - SelectedRoom.Value.Name.Value = "A very awesome room"; - SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; - }); - } } } From 9132c42f875576a4d7c9e74cccb1ecc1a9592826 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 15:58:07 +0900 Subject: [PATCH 1163/1621] Fix actions posted to the wrong channel --- osu.Game/Online/Chat/ChannelManager.cs | 4 ++-- osu.Game/Online/Chat/NowPlayingCommand.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 8507887357..3136a3960d 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -225,7 +225,7 @@ namespace osu.Game.Online.Chat switch (command) { case "np": - AddInternal(new NowPlayingCommand()); + AddInternal(new NowPlayingCommand(target)); break; case "me": @@ -235,7 +235,7 @@ namespace osu.Game.Online.Chat break; } - PostMessage(content, true); + PostMessage(content, true, target); break; case "join": diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index 926709694b..f522d8a236 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -21,6 +21,13 @@ namespace osu.Game.Online.Chat [Resolved] private Bindable currentBeatmap { get; set; } + private readonly Channel target; + + public NowPlayingCommand(Channel target) + { + this.target = target; + } + protected override void LoadComplete() { base.LoadComplete(); @@ -48,7 +55,7 @@ namespace osu.Game.Online.Chat var beatmapString = beatmap.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmap.OnlineBeatmapID} {beatmap}]" : beatmap.ToString(); - channelManager.PostMessage($"is {verb} {beatmapString}", true); + channelManager.PostMessage($"is {verb} {beatmapString}", true, target); Expire(); } } From 7a86686f40836dd58939ebbd00c0e7107f7c099c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 16:30:40 +0900 Subject: [PATCH 1164/1621] Make nullable --- osu.Game/Online/Chat/NowPlayingCommand.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index f522d8a236..7756591e03 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -23,7 +23,11 @@ namespace osu.Game.Online.Chat private readonly Channel target; - public NowPlayingCommand(Channel target) + /// + /// Creates a new to post the currently-playing beatmap to a parenting . + /// + /// The target channel to post to. If null, the currently-selected channel will be posted to. + public NowPlayingCommand(Channel target = null) { this.target = target; } From ca0eaab8e21b743ad5bdc14ec3878e7658d5ca97 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 16:30:46 +0900 Subject: [PATCH 1165/1621] Add test --- .../Chat/TestSceneChannelManager.cs | 104 ++++++++++++++++++ .../Online/API/Requests/PostMessageRequest.cs | 10 +- 2 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Chat/TestSceneChannelManager.cs diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs new file mode 100644 index 0000000000..b81c39933d --- /dev/null +++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs @@ -0,0 +1,104 @@ +// 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.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Chat; +using osu.Game.Tests.Visual; +using osu.Game.Users; + +namespace osu.Game.Tests.Chat +{ + [HeadlessTest] + public class TestSceneChannelManager : OsuTestScene + { + private ChannelManager channelManager; + private int currentMessageId; + + [SetUp] + public void Setup() => Schedule(() => + { + var container = new ChannelManagerContainer(); + Child = container; + channelManager = container.ChannelManager; + }); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("register request handling", () => + { + currentMessageId = 0; + + ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case JoinChannelRequest _: + return true; + + case PostMessageRequest postMessage: + postMessage.TriggerSuccess(new Message(++currentMessageId) + { + IsAction = postMessage.Message.IsAction, + ChannelId = postMessage.Message.ChannelId, + Content = postMessage.Message.Content, + Links = postMessage.Message.Links, + Timestamp = postMessage.Message.Timestamp, + Sender = postMessage.Message.Sender + }); + + return true; + } + + return false; + }; + }); + } + + [Test] + public void TestCommandsPostedToCorrectChannelWhenNotCurrent() + { + Channel channel1 = null; + Channel channel2 = null; + + AddStep("join 2 rooms", () => + { + channelManager.JoinChannel(channel1 = createChannel(1, ChannelType.Public)); + channelManager.JoinChannel(channel2 = createChannel(2, ChannelType.Public)); + }); + + AddStep("select channel 1", () => channelManager.CurrentChannel.Value = channel1); + + AddStep("post /me command to channel 2", () => channelManager.PostCommand("me dances", channel2)); + AddAssert("/me command received by channel 2", () => channel2.Messages.Last().Content == "dances"); + + AddStep("post /np command to channel 2", () => channelManager.PostCommand("np", channel2)); + AddAssert("/np command received by channel 2", () => channel2.Messages.Last().Content.Contains("is listening to")); + } + + private Channel createChannel(int id, ChannelType type) => new Channel(new User()) + { + Id = id, + Name = $"Channel {id}", + Topic = $"Topic of channel {id} with type {type}", + Type = type, + }; + + private class ChannelManagerContainer : CompositeDrawable + { + [Cached] + public ChannelManager ChannelManager { get; } = new ChannelManager(); + + public ChannelManagerContainer() + { + InternalChild = ChannelManager; + } + } + } +} diff --git a/osu.Game/Online/API/Requests/PostMessageRequest.cs b/osu.Game/Online/API/Requests/PostMessageRequest.cs index 84ab873acf..5d508a4cdf 100644 --- a/osu.Game/Online/API/Requests/PostMessageRequest.cs +++ b/osu.Game/Online/API/Requests/PostMessageRequest.cs @@ -9,11 +9,11 @@ namespace osu.Game.Online.API.Requests { public class PostMessageRequest : APIRequest { - private readonly Message message; + public readonly Message Message; public PostMessageRequest(Message message) { - this.message = message; + Message = message; } protected override WebRequest CreateWebRequest() @@ -21,12 +21,12 @@ namespace osu.Game.Online.API.Requests var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; - req.AddParameter(@"is_action", message.IsAction.ToString().ToLowerInvariant()); - req.AddParameter(@"message", message.Content); + req.AddParameter(@"is_action", Message.IsAction.ToString().ToLowerInvariant()); + req.AddParameter(@"message", Message.Content); return req; } - protected override string Target => $@"chat/channels/{message.ChannelId}/messages"; + protected override string Target => $@"chat/channels/{Message.ChannelId}/messages"; } } From 8bcb4d13fb66a5c49101c9d10b424ca0bd3f6fed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 17:21:09 +0900 Subject: [PATCH 1166/1621] Fix multiple tests eating host exceptions --- osu.Game.Tests/ImportTest.cs | 3 ++- .../NonVisual/CustomTourneyDirectoryTest.cs | 3 ++- osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs index ea351e0d45..e888f51e98 100644 --- a/osu.Game.Tests/ImportTest.cs +++ b/osu.Game.Tests/ImportTest.cs @@ -17,7 +17,8 @@ namespace osu.Game.Tests protected virtual TestOsuGameBase LoadOsuIntoHost(GameHost host, bool withBeatmap = false) { var osu = new TestOsuGameBase(withBeatmap); - Task.Run(() => host.Run(osu)); + Task.Run(() => host.Run(osu)) + .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 46c3b8bc3b..61f8511e3c 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -149,7 +149,8 @@ namespace osu.Game.Tournament.Tests.NonVisual private TournamentGameBase loadOsu(GameHost host) { var osu = new TournamentGameBase(); - Task.Run(() => host.Run(osu)); + Task.Run(() => host.Run(osu)) + .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); return osu; } diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 4c5f5a7a1a..e4eb5a36fb 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -55,7 +55,8 @@ namespace osu.Game.Tournament.Tests.NonVisual private TournamentGameBase loadOsu(GameHost host) { var osu = new TournamentGameBase(); - Task.Run(() => host.Run(osu)); + Task.Run(() => host.Run(osu)) + .ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted); waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); return osu; } From 7997d570308d39ea6c3e1c95beba03c59f119976 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 29 Jun 2021 11:25:35 +0300 Subject: [PATCH 1167/1621] Mention interfaces being "markers" for the time being --- osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs | 2 +- osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs index 07ca93126d..fe0abc453d 100644 --- a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs +++ b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs @@ -4,7 +4,7 @@ namespace osu.Game.Rulesets.Osu.Mods { /// - /// Any mod which completely hides the approach circles. Used for incompatibility with . + /// Marker interface for any mod which completely hides the approach circles. Used for incompatibility with . /// public interface IHidesApproachCircles { diff --git a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs index f2bd836835..3220ebf378 100644 --- a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs +++ b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs @@ -4,7 +4,7 @@ namespace osu.Game.Rulesets.Osu.Mods { /// - /// Any mod which requires the approach circles to be visible. Used for incompatibility with . + /// Marker interface for any mod which requires the approach circles to be visible. Used for incompatibility with . /// public interface IRequiresApproachCircles { From bfdbe3c3fe534fc3b0060f4253635065f30b074b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 17:29:25 +0900 Subject: [PATCH 1168/1621] Fix clocks getting added a second time --- .../Multiplayer/Spectate/CatchUpSyncManager.cs | 7 ++++++- .../Spectate/MultiSpectatorScreen.cs | 17 ++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index 781123f5bb..94278a47b6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -54,7 +55,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate MasterClock = master; } - public void AddPlayerClock(ISpectatorPlayerClock clock) => playerClocks.Add(clock); + public void AddPlayerClock(ISpectatorPlayerClock clock) + { + Debug.Assert(!playerClocks.Contains(clock)); + playerClocks.Add(clock); + } public void RemovePlayerClock(ISpectatorPlayerClock clock) => playerClocks.Remove(clock); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 013e5551cf..e709cba0ee 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -101,7 +101,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Expanded = { Value = true }, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - }, leaderboardContainer.Add); + }, l => + { + foreach (var instance in instances) + leaderboard.AddClock(instance.UserId, instance.GameplayClock); + + leaderboardContainer.Add(leaderboard); + }); syncManager.ReadyToStart += onReadyToStart; syncManager.MasterState.BindValueChanged(onMasterStateChanged, true); @@ -166,14 +172,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } protected override void StartGameplay(int userId, GameplayState gameplayState) - { - var instance = instances.Single(i => i.UserId == userId); - - instance.LoadScore(gameplayState.Score); - - syncManager.AddPlayerClock(instance.GameplayClock); - leaderboard.AddClock(instance.UserId, instance.GameplayClock); - } + => instances.Single(i => i.UserId == userId).LoadScore(gameplayState.Score); protected override void EndGameplay(int userId) { From dbe3150f88465c25dbba23c2111e97968e50b26a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 29 Jun 2021 11:37:03 +0300 Subject: [PATCH 1169/1621] Mention about marker interfaces even more explicitly --- osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs | 6 +++++- osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs index fe0abc453d..4a3b187e83 100644 --- a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs +++ b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs @@ -4,8 +4,12 @@ namespace osu.Game.Rulesets.Osu.Mods { /// - /// Marker interface for any mod which completely hides the approach circles. Used for incompatibility with . + /// Marker interface for any mod which completely hides the approach circles. + /// Used for incompatibility with . /// + /// + /// Note that this is only a marker interface for incompatibility purposes, it does not change any gameplay behaviour. + /// public interface IHidesApproachCircles { } diff --git a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs index 3220ebf378..1458abfe05 100644 --- a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs +++ b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs @@ -4,8 +4,12 @@ namespace osu.Game.Rulesets.Osu.Mods { /// - /// Marker interface for any mod which requires the approach circles to be visible. Used for incompatibility with . + /// Marker interface for any mod which requires the approach circles to be visible. + /// Used for incompatibility with . /// + /// + /// Note that this is only a marker interface for incompatibility purposes, it does not change any gameplay behaviour. + /// public interface IRequiresApproachCircles { } From 1b2d00f796735f1fa94234cd5ff91778079087f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 20:13:39 +0900 Subject: [PATCH 1170/1621] Trigger successes --- osu.Game.Tests/Chat/TestSceneChannelManager.cs | 3 ++- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs index b81c39933d..0ec21a4c7b 100644 --- a/osu.Game.Tests/Chat/TestSceneChannelManager.cs +++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs @@ -39,7 +39,8 @@ namespace osu.Game.Tests.Chat { switch (req) { - case JoinChannelRequest _: + case JoinChannelRequest joinChannel: + joinChannel.TriggerSuccess(); return true; case PostMessageRequest postMessage: diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 3971146ff8..a1549dfbce 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -82,7 +82,8 @@ namespace osu.Game.Tests.Visual.Online { switch (req) { - case JoinChannelRequest _: + case JoinChannelRequest joinChannel: + joinChannel.TriggerSuccess(); return true; } From 9acc5e38bb1574fe0ff9226cd8ff4221a3e29d49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 20:16:57 +0900 Subject: [PATCH 1171/1621] Add basic logging for osu! storage migration When looking into the test failure at https://github.com/ppy/osu/runs/2940065457, it became apparent that we are not showing the migration process anywhere in logs. It's the cause of many issues, and we would want to see this in CI and user logs when occurring. --- osu.Game/OsuGameBase.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index bf1b449292..7954eafdca 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -422,11 +422,15 @@ namespace osu.Game public void Migrate(string path) { + Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""..."); + using (realmFactory.BlockAllOperations()) { contextFactory.FlushConnections(); (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); } + + Logger.Log(@"Migration complete!"); } protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); From 2f1203085b4fc015dbda854510f274564ad18b21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Jun 2021 20:21:31 +0900 Subject: [PATCH 1172/1621] Also add logging of realm block/flush operations --- osu.Game/Database/RealmContextFactory.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 71617b258d..fb5e2faff8 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -89,12 +89,18 @@ namespace osu.Game.Database if (IsDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); + Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + blockingLock.Wait(); flushContexts(); return new InvokeOnDisposal(this, endBlockingSection); - static void endBlockingSection(RealmContextFactory factory) => factory.blockingLock.Release(); + static void endBlockingSection(RealmContextFactory factory) + { + factory.blockingLock.Release(); + Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); + } } protected override void Update() @@ -147,6 +153,8 @@ namespace osu.Game.Database private void flushContexts() { + Logger.Log(@"Flushing realm contexts...", LoggingTarget.Database); + var previousContext = context; context = null; @@ -155,6 +163,8 @@ namespace osu.Game.Database Thread.Sleep(50); previousContext?.Dispose(); + + Logger.Log(@"Realm contexts flushed.", LoggingTarget.Database); } protected override void Dispose(bool isDisposing) From 3a002e357a63caa42578c39c8cf6cf8082c43cb9 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 29 Jun 2021 19:28:21 +0700 Subject: [PATCH 1173/1621] rename method --- osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs index 4b0490d034..1a4f6087c7 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs @@ -63,7 +63,7 @@ namespace osu.Game.Overlays.Wiki.Markdown RelativeSizeAxes = Axes.X; } - protected override Sprite CreateSpriteImage() => new ImageSprite(); + protected override Sprite CreateImageSprite() => new ImageSprite(); private class ImageSprite : Sprite { From 6dd3c6fe9307b5d71c850ef8dbe6d1733d2e8643 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 22:45:18 +0900 Subject: [PATCH 1174/1621] Make MultiSpectatorScreen and tests more resillient to timing --- .../Multiplayer/TestSceneMultiSpectatorScreen.cs | 12 +++++++++--- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 16 +++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index a308e854c6..8c63fb8924 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -82,8 +82,8 @@ namespace osu.Game.Tests.Visual.Multiplayer start(new[] { PLAYER_1_ID, PLAYER_2_ID }); loadSpectateScreen(); - sendFrames(PLAYER_1_ID, 20); - sendFrames(PLAYER_2_ID, 10); + sendFrames(PLAYER_1_ID, 40); + sendFrames(PLAYER_2_ID, 20); checkPaused(PLAYER_2_ID, true); checkPausedInstant(PLAYER_1_ID, false); @@ -196,6 +196,7 @@ namespace osu.Game.Tests.Visual.Multiplayer sendFrames(PLAYER_1_ID, 10); sendFrames(PLAYER_2_ID, 20); + checkPaused(PLAYER_1_ID, false); assertMuted(PLAYER_1_ID, false); assertMuted(PLAYER_2_ID, true); @@ -297,7 +298,12 @@ namespace osu.Game.Tests.Visual.Multiplayer => AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); private void checkPausedInstant(int userId, bool state) - => AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); + { + checkPaused(userId, state); + + // Todo: The following should work, but is broken because SpectatorScreen retrieves the WorkingBeatmap via the BeatmapManager, bypassing the test scene clock and running real-time. + // AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); + } private void assertMuted(int userId, bool muted) => AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index e709cba0ee..2a2759e0dd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -43,6 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private PlayerGrid grid; private MultiSpectatorLeaderboard leaderboard; private PlayerArea currentAudioSource; + private bool canStartMasterClock; /// /// Creates a new . @@ -108,17 +109,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate leaderboardContainer.Add(leaderboard); }); - - syncManager.ReadyToStart += onReadyToStart; - syncManager.MasterState.BindValueChanged(onMasterStateChanged, true); } protected override void LoadComplete() { base.LoadComplete(); - masterClockContainer.Stop(); masterClockContainer.Reset(); + masterClockContainer.Stop(); + + syncManager.ReadyToStart += onReadyToStart; + syncManager.MasterState.BindValueChanged(onMasterStateChanged, true); } protected override void Update() @@ -151,6 +152,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate masterClockContainer.Seek(startTime); masterClockContainer.Start(); + + // Although the clock has been started, this flag is set to allow for later synchronisation state changes to also be able to start it. + canStartMasterClock = true; } private void onMasterStateChanged(ValueChangedEvent state) @@ -158,7 +162,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate switch (state.NewValue) { case MasterClockState.Synchronised: - masterClockContainer.Start(); + if (canStartMasterClock) + masterClockContainer.Start(); + break; case MasterClockState.TooFarAhead: From 331b7237ab5f175067a886668d2ef3de34baf664 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 22:53:31 +0900 Subject: [PATCH 1175/1621] Attempt to fix one more intermittent test failure --- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 8c63fb8924..783db49d36 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -197,8 +197,7 @@ namespace osu.Game.Tests.Visual.Multiplayer sendFrames(PLAYER_1_ID, 10); sendFrames(PLAYER_2_ID, 20); checkPaused(PLAYER_1_ID, false); - assertMuted(PLAYER_1_ID, false); - assertMuted(PLAYER_2_ID, true); + assertOneNotMuted(); checkPaused(PLAYER_1_ID, true); assertMuted(PLAYER_1_ID, true); @@ -305,6 +304,8 @@ namespace osu.Game.Tests.Visual.Multiplayer // AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); } + private void assertOneNotMuted() => AddAssert("one player not muted", () => spectatorScreen.ChildrenOfType().Count(p => !p.Mute) == 1); + private void assertMuted(int userId, bool muted) => AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted); From e9158ccc41828b1a3fc220d5f15e8df03bca09d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Jun 2021 23:23:21 +0900 Subject: [PATCH 1176/1621] Fix gameplay tests incorrectly seeking via MusicController --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index e111bb1054..1d500dcc14 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void addSeekStep(double time) { - AddStep($"seek to {time}", () => MusicController.SeekTo(time)); + AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 8ff21057b5..9da583a073 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void addSeekStep(double time) { - AddStep($"seek to {time}", () => MusicController.SeekTo(time)); + AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } From bc0ab7dd4f1313e448c63661f3252beb18595e1c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 29 Jun 2021 23:39:32 +0300 Subject: [PATCH 1177/1621] Fix `RestoreDefaultValueButton` not behaving correctly on number types --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index fe36f6ba6d..2f0c43dedb 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays { public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly IBindableWithCurrent current = IBindableWithCurrent.Create(); // this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button. public override bool AcceptsFocus => true; @@ -62,7 +62,8 @@ namespace osu.Game.Overlays Action += () => { - if (!current.Disabled) current.SetDefault(); + if (!Current.Disabled) + Current.SetDefault(); }; } @@ -96,12 +97,12 @@ namespace osu.Game.Overlays private void updateState() { - if (current == null) + if (Current == null) return; - this.FadeTo(current.IsDefault ? 0f : - hovering && !current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); - this.FadeColour(current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); + this.FadeTo(Current.IsDefault ? 0f : + hovering && !Current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); + this.FadeColour(Current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); } } } From 3f185a062234cf8f9e86511a365ea496c93d7506 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 30 Jun 2021 10:35:06 +0800 Subject: [PATCH 1178/1621] Fixed an exception when clamping large sliders --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 78a49f8e91..e0a3e83241 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -152,10 +152,15 @@ namespace osu.Game.Rulesets.Osu.Mods var prevPosition = slider.Position; - slider.Position = new Vector2( - Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right), - Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom) - ); + var newX = minMargin.Left + minMargin.Right > OsuPlayfield.BASE_SIZE.X + ? currentObjectInfo.PositionOriginal.X + : Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right); + + var newY = minMargin.Top + minMargin.Bottom > OsuPlayfield.BASE_SIZE.Y + ? currentObjectInfo.PositionOriginal.Y + : Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); + + slider.Position = new Vector2(newX, newY); currentObjectInfo.PositionRandomised = slider.Position; currentObjectInfo.EndPositionRandomised = slider.EndPosition; From 43375774487498cf412be541ce8d17c3169ee6b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Jun 2021 14:31:27 +0900 Subject: [PATCH 1179/1621] Remove unused private methods --- .../Multiplayer/TestSceneMultiSpectatorScreen.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 873f8ca35b..ea8f7813fd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -217,8 +217,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); } - private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId); - private void start(int[] userIds, int? beatmapId = null) { AddStep("start play", () => @@ -233,16 +231,6 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - private void finish(int userId) - { - AddStep("end play", () => - { - SpectatorClient.EndPlay(userId); - playingUserIds.Remove(userId); - nextFrame.Remove(userId); - }); - } - private void sendFrames(int userId, int count = 10) => sendFrames(new[] { userId }, count); private void sendFrames(int[] userIds, int count = 10) From e3344c33ea27d2269d60e06aaab6933f10763e1b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 30 Jun 2021 09:57:15 +0300 Subject: [PATCH 1180/1621] Better convey reason of "Spin In" incompatibility with "Hidden" --- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 47a771c2f1..7b2a0c6e0f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -21,8 +21,9 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Circles spin in. No approach circles."; 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(IRequiresApproachCircles), typeof(IHidesApproachCircles) }; + // todo: this mod needs to be incompatible with "hidden" due to forcing the circle to remain opaque, + // further implementation will be required for supporting that. + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModObjectScaleTween), typeof(OsuModHidden) }; private const int rotate_offset = 360; private const float rotate_starting_width = 2; @@ -49,10 +50,6 @@ namespace osu.Game.Rulesets.Osu.Mods circle.RotateTo(rotate_offset).Then().RotateTo(0, h.TimePreempt, Easing.InOutSine); circle.ScaleTo(new Vector2(rotate_starting_width, 0)).Then().ScaleTo(1, h.TimePreempt, Easing.InOutSine); - - // bypass fade in. - if (state == ArmedState.Idle) - circle.FadeIn(); } break; From 57a21dfb1ca75d4024060ccfc054a2132237907d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Jun 2021 16:11:18 +0900 Subject: [PATCH 1181/1621] 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 c845d7f276..481ddc118f 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 f047859dbb..589afb86be 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 304047ad12..a8bf0e4ab2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 4b56d94a9806b11e9017ed919cf05cf5a8620c08 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 30 Jun 2021 10:26:27 +0300 Subject: [PATCH 1182/1621] Revert accidentally pushed part of change --- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 7b2a0c6e0f..56c246953e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -50,6 +50,10 @@ namespace osu.Game.Rulesets.Osu.Mods circle.RotateTo(rotate_offset).Then().RotateTo(0, h.TimePreempt, Easing.InOutSine); circle.ScaleTo(new Vector2(rotate_starting_width, 0)).Then().ScaleTo(1, h.TimePreempt, Easing.InOutSine); + + // bypass fade in. + if (state == ArmedState.Idle) + circle.FadeIn(); } break; From e23614556eaefc9a579f7a42e6cf8f5f8f4c2dc6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Jun 2021 19:43:34 +0900 Subject: [PATCH 1183/1621] Fix slider testscene failures --- .../TestSceneSliderSnaking.cs | 102 ++++++++++-------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 1d500dcc14..744291ae09 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -37,11 +37,13 @@ namespace osu.Game.Rulesets.Osu.Tests private readonly BindableBool snakingIn = new BindableBool(); private readonly BindableBool snakingOut = new BindableBool(); + private IBeatmap beatmap; + private const double duration_of_span = 3605; private const double fade_in_modifier = -1200; protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + => new ClockBackedTestWorkingBeatmap(this.beatmap = beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); [BackgroundDependencyLoader] private void load(RulesetConfigCache configCache) @@ -51,8 +53,16 @@ namespace osu.Game.Rulesets.Osu.Tests config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); } + private Slider slider; private DrawableSlider drawableSlider; + [SetUp] + public void Setup() => Schedule(() => + { + slider = null; + drawableSlider = null; + }); + [SetUpSteps] public override void SetUpSteps() { @@ -67,21 +77,19 @@ namespace osu.Game.Rulesets.Osu.Tests base.SetUpSteps(); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); - double startTime = hitObjects[sliderIndex].StartTime; - addSeekStep(startTime); - retrieveDrawableSlider((Slider)hitObjects[sliderIndex]); + retrieveSlider(sliderIndex); setSnaking(true); - ensureSnakingIn(startTime + fade_in_modifier); + addEnsureSnakingInSteps(() => slider.StartTime + fade_in_modifier); for (int i = 0; i < sliderIndex; i++) { // non-final repeats should not snake out - ensureNoSnakingOut(startTime, i); + addEnsureNoSnakingOutStep(() => slider.StartTime, i); } // final repeat should snake out - ensureSnakingOut(startTime, sliderIndex); + addEnsureSnakingOutSteps(() => slider.StartTime, sliderIndex); } [TestCase(0)] @@ -93,17 +101,15 @@ namespace osu.Game.Rulesets.Osu.Tests base.SetUpSteps(); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); - double startTime = hitObjects[sliderIndex].StartTime; - addSeekStep(startTime); - retrieveDrawableSlider((Slider)hitObjects[sliderIndex]); + retrieveSlider(sliderIndex); setSnaking(false); - ensureNoSnakingIn(startTime + fade_in_modifier); + addEnsureNoSnakingInSteps(() => slider.StartTime + fade_in_modifier); for (int i = 0; i <= sliderIndex; i++) { // no snaking out ever, including final repeat - ensureNoSnakingOut(startTime, i); + addEnsureNoSnakingOutStep(() => slider.StartTime, i); } } @@ -116,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests // repeat might have a chance to update its position depending on where in the frame its hit, // so some leniency is allowed here instead of checking strict equality - checkPositionChange(16600, sliderRepeat, positionAlmostSame); + addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionAlmostSame); } [Test] @@ -126,38 +132,46 @@ namespace osu.Game.Rulesets.Osu.Tests setSnaking(true); base.SetUpSteps(); - checkPositionChange(16600, sliderRepeat, positionDecreased); + addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionDecreased); } - private void retrieveDrawableSlider(Slider slider) => AddUntilStep($"retrieve slider @ {slider.StartTime}", () => - (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null); - - private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased); - private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame); - - private void ensureSnakingOut(double startTime, int repeatIndex) + private void retrieveSlider(int index) { - var repeatTime = timeAtRepeat(startTime, repeatIndex); + AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]); + addSeekStep(() => slider); + retrieveDrawableSlider(() => slider); + } + private void retrieveDrawableSlider(Func getSliderFunc) + { + AddUntilStep("retrieve drawable slider", () => + (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == getSliderFunc())) != null); + } + + private void addEnsureSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionIncreased); + private void addEnsureNoSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionRemainsSame); + + private void addEnsureSnakingOutSteps(Func startTime, int repeatIndex) + { if (repeatIndex % 2 == 0) - checkPositionChange(repeatTime, sliderStart, positionIncreased); + addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), getSliderStart, positionIncreased); else - checkPositionChange(repeatTime, sliderEnd, positionDecreased); + addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), getSliderEnd, positionDecreased); } - private void ensureNoSnakingOut(double startTime, int repeatIndex) => - checkPositionChange(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame); + private void addEnsureNoSnakingOutStep(Func startTime, int repeatIndex) + => addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame); - private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex; - private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)sliderStart : sliderEnd; + private Func timeAtRepeat(Func startTime, int repeatIndex) => () => startTime() + 100 + duration_of_span * repeatIndex; + private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)getSliderStart : getSliderEnd; - private List sliderCurve => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; - private Vector2 sliderStart() => sliderCurve.First(); - private Vector2 sliderEnd() => sliderCurve.Last(); + private List getSliderCurve() => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; + private Vector2 getSliderStart() => getSliderCurve().First(); + private Vector2 getSliderEnd() => getSliderCurve().Last(); - private Vector2 sliderRepeat() + private Vector2 getSliderRepeat() { - var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == hitObjects[1]); + var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == beatmap.HitObjects[1]); var repeat = drawable.ChildrenOfType>().First().Children.First(); return repeat.Position; } @@ -167,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y; private bool positionAlmostSame(Vector2 previous, Vector2 current) => Precision.AlmostEquals(previous, current, 1); - private void checkPositionChange(double startTime, Func positionToCheck, Func positionAssertion) + private void addCheckPositionChangeSteps(Func startTime, Func positionToCheck, Func positionAssertion) { Vector2 previousPosition = Vector2.Zero; @@ -176,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(startTime); AddStep($"save {positionDescription} position", () => previousPosition = positionToCheck.Invoke()); - addSeekStep(startTime + 100); + addSeekStep(() => startTime() + 100); AddAssert($"{positionDescription} {assertionDescription}", () => { var currentPosition = positionToCheck.Invoke(); @@ -193,19 +207,21 @@ namespace osu.Game.Rulesets.Osu.Tests }); } - private void addSeekStep(double time) + private void addSeekStep(Func slider) { - AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); - - AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + AddStep("seek to slider", () => Player.GameplayClockContainer.Seek(slider().StartTime)); + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(slider().StartTime, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap + private void addSeekStep(Func time) { - HitObjects = hitObjects - }; + AddStep("seek to time", () => Player.GameplayClockContainer.Seek(time())); + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time(), Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + } - private readonly List hitObjects = new List + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = createHitObjects() }; + + private List createHitObjects() => new List { new Slider { From b94d88be5078457279ac09df1086950a335ee8b7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Jun 2021 19:49:51 +0900 Subject: [PATCH 1184/1621] Make method static to better define its usage --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 744291ae09..7dafecfced 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -221,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Tests protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = createHitObjects() }; - private List createHitObjects() => new List + private static List createHitObjects() => new List { new Slider { From 2c1f788f2de20f495baf0e2631e54163824a408c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Jun 2021 19:52:25 +0900 Subject: [PATCH 1185/1621] Merge methods --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 7dafecfced..3252e6d912 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -139,13 +139,8 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]); addSeekStep(() => slider); - retrieveDrawableSlider(() => slider); - } - - private void retrieveDrawableSlider(Func getSliderFunc) - { AddUntilStep("retrieve drawable slider", () => - (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == getSliderFunc())) != null); + (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null); } private void addEnsureSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionIncreased); From aa7405afa1f8c467c0534cd20101e7e00fa752ab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Jun 2021 20:16:57 +0900 Subject: [PATCH 1186/1621] Increase number of sent frames to prevent timing issues --- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 5112029873..b8db4067fb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // Send initial frames for both players. A few more for player 1. sendFrames(PLAYER_1_ID, 1000); - sendFrames(PLAYER_2_ID, 10); + sendFrames(PLAYER_2_ID, 30); checkPausedInstant(PLAYER_1_ID, false); checkPausedInstant(PLAYER_2_ID, false); From 4a54e7cdb83db4eb7f86d9af3f2e077d7668088f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 18:30:12 +0900 Subject: [PATCH 1187/1621] Add tests covering score preparation flow --- .../TestScenePlayerScorePreparation.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs new file mode 100644 index 0000000000..8ea5f3f9ee --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestScenePlayerScorePreparation : OsuPlayerTestScene + { + protected override bool AllowFail => false; + + protected new PreparingPlayer Player => (PreparingPlayer)base.Player; + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + // Ensure track has actually running before attempting to seek + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + } + + [Test] + public void TestPreparationOnResults() + { + AddUntilStep("wait for preparation", () => Player.PreparationCompleted); + } + + [Test] + public void TestPreparationOnExit() + { + AddStep("exit", () => Player.Exit()); + AddUntilStep("wait for preparation", () => Player.PreparationCompleted); + } + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new PreparingPlayer(); + + public class PreparingPlayer : TestPlayer + { + public bool PreparationCompleted { get; private set; } + + public bool ResultsCreated { get; private set; } + + public PreparingPlayer() + : base(true, true) + { + } + + protected override ResultsScreen CreateResults(ScoreInfo score) + { + var results = base.CreateResults(score); + ResultsCreated = true; + return results; + } + + protected override Task PrepareScoreForResultsAsync(Score score) + { + PreparationCompleted = true; + return base.PrepareScoreForResultsAsync(score); + } + } + } +} From 00d3baef1191f67dc062ee5205804372e1bf4ca5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Jun 2021 20:23:48 +0900 Subject: [PATCH 1188/1621] Exit handling flow --- osu.Game/Screens/Play/SubmittingPlayer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index b843915a7c..2ee116a6a3 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -102,6 +102,11 @@ namespace osu.Game.Screens.Play /// Whether gameplay should be immediately exited as a result. Returning false allows the gameplay session to continue. Defaults to true. protected virtual bool HandleTokenRetrievalFailure(Exception exception) => true; + public override bool OnExiting(IScreen next) + { + return base.OnExiting(next); + } + protected override async Task PrepareScoreForResultsAsync(Score score) { await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); From 285c49be7ec8ac465081d91294bada9b83ba97d0 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 30 Jun 2021 20:28:27 +0200 Subject: [PATCH 1189/1621] Localise dashboard overlay header. --- .../Dashboard/DashboardOverlayHeader.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index 3314ed957a..ce4ec27225 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -1,7 +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.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Dashboard { @@ -13,13 +16,14 @@ namespace osu.Game.Overlays.Dashboard { public DashboardTitle() { - Title = "dashboard"; + Title = HomeStrings.UserTitle; Description = "view your friends and other information"; IconTexture = "Icons/Hexacons/social"; } } } + [LocalisableEnum(typeof(DashboardOverlayTabsEnumLocalisationMapper))] public enum DashboardOverlayTabs { Friends, @@ -27,4 +31,22 @@ namespace osu.Game.Overlays.Dashboard [Description("Currently Playing")] CurrentlyPlaying } + + public class DashboardOverlayTabsEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(DashboardOverlayTabs value) + { + switch (value) + { + case DashboardOverlayTabs.Friends: + return FriendsStrings.TitleCompact; + + case DashboardOverlayTabs.CurrentlyPlaying: + return @"Currently Playing"; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } From 913f7602e4829306aa6aa5e0faa5a97bd6522568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Jun 2021 20:41:08 +0200 Subject: [PATCH 1190/1621] Change seed control type in line with changes --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 3ad432a40b..12daa44477 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -17,6 +17,7 @@ using osu.Game.Beatmaps.Timing; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; - [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SeedSettingsControl))] + [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))] public Bindable Seed { get; } = new Bindable { Default = null, From f2287ba0224badf454d491370d3ad49a894eeecf Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 30 Jun 2021 21:16:21 +0200 Subject: [PATCH 1191/1621] Localise friends online status stream control. --- .../Changelog/ChangelogUpdateStreamItem.cs | 7 ++--- .../Friends/FriendsOnlineStatusItem.cs | 6 +++-- .../Dashboard/Friends/OnlineStatus.cs | 26 +++++++++++++++++++ osu.Game/Overlays/OverlayStreamItem.cs | 7 ++--- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs index f8e1ac0c84..cb144defbf 100644 --- a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs +++ b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using Humanizer; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; using osuTK.Graphics; @@ -17,11 +18,11 @@ namespace osu.Game.Overlays.Changelog Width *= 2; } - protected override string MainText => Value.DisplayName; + protected override LocalisableString MainText => Value.DisplayName; - protected override string AdditionalText => Value.LatestBuild.DisplayVersion; + protected override LocalisableString AdditionalText => Value.LatestBuild.DisplayVersion; - protected override string InfoText => Value.LatestBuild.Users > 0 ? $"{"user".ToQuantity(Value.LatestBuild.Users, "N0")} online" : null; + protected override LocalisableString InfoText => Value.LatestBuild.Users > 0 ? $"{"user".ToQuantity(Value.LatestBuild.Users, "N0")} online" : null; protected override Color4 GetBarColour(OsuColour colours) => Value.Colour; } diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs b/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs index 7e902203f8..11dcb93e6f 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Extensions; +using osu.Framework.Localisation; using osu.Game.Graphics; using osuTK.Graphics; @@ -14,9 +16,9 @@ namespace osu.Game.Overlays.Dashboard.Friends { } - protected override string MainText => Value.Status.ToString(); + protected override LocalisableString MainText => Value.Status.GetLocalisableDescription(); - protected override string AdditionalText => Value.Count.ToString(); + protected override LocalisableString AdditionalText => Value.Count.ToString(); protected override Color4 GetBarColour(OsuColour colours) { diff --git a/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs b/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs index 6f2f55a6ed..4b5a7ef066 100644 --- a/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs +++ b/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.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.Dashboard.Friends { + [LocalisableEnum(typeof(OnlineStatusEnumLocalisationMapper))] public enum OnlineStatus { All, Online, Offline } + + public class OnlineStatusEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(OnlineStatus value) + { + switch (value) + { + case OnlineStatus.All: + return SortStrings.All; + + case OnlineStatus.Online: + return UsersStrings.StatusOnline; + + case OnlineStatus.Offline: + return UsersStrings.StatusOffline; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/OverlayStreamItem.cs b/osu.Game/Overlays/OverlayStreamItem.cs index cd1391a3d8..56502ff70f 100644 --- a/osu.Game/Overlays/OverlayStreamItem.cs +++ b/osu.Game/Overlays/OverlayStreamItem.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; using osuTK.Graphics; +using osu.Framework.Localisation; namespace osu.Game.Overlays { @@ -88,11 +89,11 @@ namespace osu.Game.Overlays SelectedItem.BindValueChanged(_ => updateState(), true); } - protected abstract string MainText { get; } + protected abstract LocalisableString MainText { get; } - protected abstract string AdditionalText { get; } + protected abstract LocalisableString AdditionalText { get; } - protected virtual string InfoText => string.Empty; + protected virtual LocalisableString InfoText => string.Empty; protected abstract Color4 GetBarColour(OsuColour colours); From 8da6ecf6a9dd40ee211d349dd9dba6f64e1a6501 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 30 Jun 2021 21:28:07 +0200 Subject: [PATCH 1192/1621] Localise OverlayPanelDisplayStyle tab control. --- .../Dashboard/Friends/UserSortTabControl.cs | 25 +++++++++++++++++ .../OverlayPanelDisplayStyleControl.cs | 27 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs b/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs index 3a5f65212d..dc756e2957 100644 --- a/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs +++ b/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs @@ -1,7 +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.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Dashboard.Friends { @@ -9,6 +12,7 @@ namespace osu.Game.Overlays.Dashboard.Friends { } + [LocalisableEnum(typeof(UserSortCriteriaEnumLocalisationMappper))] public enum UserSortCriteria { [Description(@"Recently Active")] @@ -16,4 +20,25 @@ namespace osu.Game.Overlays.Dashboard.Friends Rank, Username } + + public class UserSortCriteriaEnumLocalisationMappper : EnumLocalisationMapper + { + public override LocalisableString Map(UserSortCriteria value) + { + switch (value) + { + case UserSortCriteria.LastVisit: + return SortStrings.LastVisit; + + case UserSortCriteria.Rank: + return SortStrings.Rank; + + case UserSortCriteria.Username: + return SortStrings.Username; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } diff --git a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs index 0ece96b56c..c2268ff43c 100644 --- a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs +++ b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs @@ -12,6 +12,9 @@ using osu.Framework.Allocation; using osuTK.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; +using System; +using osu.Game.Resources.Localisation.Web; +using osu.Framework.Extensions; namespace osu.Game.Overlays { @@ -57,7 +60,7 @@ namespace osu.Game.Overlays [Resolved] private OverlayColourProvider colourProvider { get; set; } - public LocalisableString TooltipText => $@"{Value} view"; + public LocalisableString TooltipText => Value.GetLocalisableDescription(); private readonly SpriteIcon icon; @@ -98,10 +101,32 @@ namespace osu.Game.Overlays } } + [LocalisableEnum(typeof(OverlayPanelDisplayStyleEnumLocalisationMapper))] public enum OverlayPanelDisplayStyle { Card, List, Brick } + + public class OverlayPanelDisplayStyleEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(OverlayPanelDisplayStyle value) + { + switch (value) + { + case OverlayPanelDisplayStyle.Card: + return UsersStrings.ViewModeCard; + + case OverlayPanelDisplayStyle.List: + return UsersStrings.ViewModeList; + + case OverlayPanelDisplayStyle.Brick: + return UsersStrings.ViewModeBrick; + + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } } From 939b2baafecde49976175bd30f977618ef52c1e5 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 30 Jun 2021 22:19:04 +0200 Subject: [PATCH 1193/1621] Localise overlay "Scroll to top" button. --- osu.Game/Overlays/OverlayScrollContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index c5b4cc3645..ca5fc90027 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -118,7 +119,7 @@ namespace osu.Game.Overlays } }); - TooltipText = "Scroll to top"; + TooltipText = CommonStrings.ButtonsBackToTop; } [BackgroundDependencyLoader] From 583242d96d343bb342f524e3bc0275d111d945af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Jun 2021 13:05:26 +0200 Subject: [PATCH 1194/1621] Add osu!-styled colour picker control --- .../UserInterface/TestSceneColourPicker.cs | 82 +++++++++++++ .../UserInterfaceV2/OsuColourPicker.cs | 19 +++ .../UserInterfaceV2/OsuHSVColourPicker.cs | 109 ++++++++++++++++++ .../UserInterfaceV2/OsuHexColourPicker.cs | 57 +++++++++ 4 files changed, 267 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuColourPicker.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs new file mode 100644 index 0000000000..634e45e7a9 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs @@ -0,0 +1,82 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneColourPicker : OsuTestScene + { + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create pickers", () => Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"No OverlayColourProvider", + Font = OsuFont.Default.With(size: 40) + }, + new OsuColourPicker + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + } + }, + new ColourProvidingContainer(OverlayColourScheme.Blue) + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"With blue OverlayColourProvider", + Font = OsuFont.Default.With(size: 40) + }, + new OsuColourPicker + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + } + } + } + } + }); + } + + private class ColourProvidingContainer : Container + { + [Cached] + private OverlayColourProvider provider { get; } + + public ColourProvidingContainer(OverlayColourScheme colourScheme) + { + provider = new OverlayColourProvider(colourScheme); + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuColourPicker.cs new file mode 100644 index 0000000000..5394e5d0aa --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuColourPicker.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.Graphics.UserInterface; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class OsuColourPicker : ColourPicker + { + public OsuColourPicker() + { + CornerRadius = 10; + Masking = true; + } + + protected override HSVColourPicker CreateHSVColourPicker() => new OsuHSVColourPicker(); + protected override HexColourPicker CreateHexColourPicker() => new OsuHexColourPicker(); + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs new file mode 100644 index 0000000000..2a399cfaf8 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs @@ -0,0 +1,109 @@ +// 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.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class OsuHSVColourPicker : HSVColourPicker + { + protected override HueSelector CreateHueSelector() => new OsuHueSelector(); + protected override SaturationValueSelector CreateSaturationValueSelector() => new OsuSaturationValueSelector(); + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour osuColour) + { + Background.Colour = colourProvider?.Dark5 ?? osuColour.GreySeafoamDark; + + Content.Padding = new MarginPadding(10); + Content.Spacing = new Vector2(0, 10); + } + + private class OsuHueSelector : HueSelector + { + public OsuHueSelector() + { + Margin = new MarginPadding + { + Bottom = 15 + }; + + SliderBar.CornerRadius = SliderBar.Height / 2; + SliderBar.Masking = true; + } + + protected override Drawable CreateSliderNub() => new SliderNub(); + + private class SliderNub : CompositeDrawable + { + public SliderNub() + { + InternalChild = new Triangle + { + Width = 20, + Height = 15, + Anchor = Anchor.BottomCentre, + Origin = Anchor.TopCentre + }; + } + } + } + + private class OsuSaturationValueSelector : SaturationValueSelector + { + public OsuSaturationValueSelector() + { + SelectionArea.CornerRadius = 10; + SelectionArea.Masking = true; + // purposefully use hard non-AA'd masking to avoid edge artifacts. + SelectionArea.MaskingSmoothness = 0; + } + + protected override Marker CreateMarker() => new OsuMarker(); + + private class OsuMarker : Marker + { + private readonly Box previewBox; + + public OsuMarker() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new CircularContainer + { + Size = new Vector2(20), + Masking = true, + BorderColour = Colour4.White, + BorderThickness = 3, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0, 1), + Radius = 3, + Colour = Colour4.Black.Opacity(0.3f) + }, + Child = previewBox = new Box + { + RelativeSizeAxes = Axes.Both + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(colour => previewBox.Colour = colour.NewValue, true); + } + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs new file mode 100644 index 0000000000..331a1b67c9 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs @@ -0,0 +1,57 @@ +// 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.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class OsuHexColourPicker : HexColourPicker + { + public OsuHexColourPicker() + { + Padding = new MarginPadding(20); + Spacing = 20; + } + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour osuColour) + { + Background.Colour = overlayColourProvider?.Dark6 ?? osuColour.GreySeafoamDarker; + } + + protected override TextBox CreateHexCodeTextBox() => new OsuTextBox(); + protected override ColourPreview CreateColourPreview() => new OsuColourPreview(); + + private class OsuColourPreview : ColourPreview + { + private readonly Box preview; + + public OsuColourPreview() + { + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = preview = new Box + { + RelativeSizeAxes = Axes.Both + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(colour => preview.Colour = colour.NewValue, true); + } + } + } +} From 30a7b034be65e7c7c83ad85518004cd3372d0583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 1 Jul 2021 00:30:43 +0200 Subject: [PATCH 1195/1621] Add HSV abbreviation to team-shared collection --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 62751cebb1..d2c5b1223c 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -308,6 +308,7 @@ GL GLSL HID + HSV HTML HUD ID From 3c1f0452a2263faa5b6e017fbf02aff40c4b4c3d Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 1 Jul 2021 10:06:14 +0800 Subject: [PATCH 1196/1621] Refactor and rename `getMinSliderMargin` to `getSliderBoundingBox`. --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 57 ++++++++++++---------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index e0a3e83241..71c070d91b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -148,19 +148,14 @@ namespace osu.Game.Rulesets.Osu.Mods /// The that this slider has been shifted by. private Vector2 moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) { - var minMargin = getMinSliderMargin(slider); + var minMargin = getSliderBoundingBox(slider); var prevPosition = slider.Position; - var newX = minMargin.Left + minMargin.Right > OsuPlayfield.BASE_SIZE.X - ? currentObjectInfo.PositionOriginal.X - : Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right); - - var newY = minMargin.Top + minMargin.Bottom > OsuPlayfield.BASE_SIZE.Y - ? currentObjectInfo.PositionOriginal.Y - : Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); - - slider.Position = new Vector2(newX, newY); + slider.Position = new Vector2( + Math.Clamp(slider.Position.X, minMargin.Left, minMargin.Right), + Math.Clamp(slider.Position.Y, minMargin.Top, minMargin.Bottom) + ); currentObjectInfo.PositionRandomised = slider.Position; currentObjectInfo.EndPositionRandomised = slider.EndPosition; @@ -190,34 +185,44 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Calculates the min. distances from the 's position to the playfield border for the slider to be fully inside of the playfield. + /// Calculates the bounding box of a 's position for the slider to be fully inside of the playfield. /// - private MarginPadding getMinSliderMargin(Slider slider) + private RectangleF getSliderBoundingBox(Slider slider) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); - var minMargin = new MarginPadding(); + var box = new RectangleF(); foreach (var pos in pathPositions) { - minMargin.Left = Math.Max(minMargin.Left, -pos.X); - minMargin.Right = Math.Max(minMargin.Right, pos.X); - minMargin.Top = Math.Max(minMargin.Top, -pos.Y); - minMargin.Bottom = Math.Max(minMargin.Bottom, pos.Y); + box.X = Math.Max(box.X, -pos.X); + box.Y = Math.Max(box.Y, -pos.Y); + box.Width = Math.Min(box.Width, OsuPlayfield.BASE_SIZE.X - pos.X - box.X); + box.Height = Math.Min(box.Height, OsuPlayfield.BASE_SIZE.Y - pos.Y - box.Y); } - minMargin.Left = Math.Min(minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right); - minMargin.Top = Math.Min(minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom); - var radius = (float)slider.Radius; - minMargin.Left += radius; - minMargin.Right += radius; - minMargin.Top += radius; - minMargin.Bottom += radius; + box.X += radius; + box.Y += radius; + box.Width -= radius * 2; + box.Height -= radius * 2; - return minMargin; + // If the slider is larger than the playfield, force the slider to stay at its original position + if (box.Width < 0) + { + box.Width = 0; + box.X = slider.Position.X; + } + + if (box.Height < 0) + { + box.Height = 0; + box.Y = slider.Position.Y; + } + + return box; } /// From 328dcb4d6b76e546d20acbb2a46b4b2d2419e54f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 1 Jul 2021 10:07:26 +0800 Subject: [PATCH 1197/1621] Use `Math.Clamp` instead of `MathHelper.Clamp` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 71c070d91b..f4358118c7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -243,8 +243,8 @@ namespace osu.Game.Rulesets.Osu.Mods private Vector2 clampToPlayfield(Vector2 position, float radius) { - position.X = MathHelper.Clamp(position.X, radius, OsuPlayfield.BASE_SIZE.X - radius); - position.Y = MathHelper.Clamp(position.Y, radius, OsuPlayfield.BASE_SIZE.Y - radius); + position.X = Math.Clamp(position.X, radius, OsuPlayfield.BASE_SIZE.X - radius); + position.Y = Math.Clamp(position.Y, radius, OsuPlayfield.BASE_SIZE.Y - radius); return position; } From 6e1839fcf2e48f90806bdebf3953d8152c3eab4f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 1 Jul 2021 10:08:28 +0800 Subject: [PATCH 1198/1621] Rename `shift_object_count` to `objects_to_shift_before_slider` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index f4358118c7..844d8d76a1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Number of previous hit circles to be shifted together when a slider needs to be moved. /// - private const int shift_object_count = 10; + private const int objects_to_shift_before_slider = 10; private Random rng; @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var toBeShifted = new List(); - for (int j = i - 1; j >= i - shift_object_count && j >= 0; j--) + for (int j = i - 1; j >= i - objects_to_shift_before_slider && j >= 0; j--) { // only shift hit circles if (!(hitObjects[j] is HitCircle)) break; From 7585f1f79088b63e30cebc866757c7e248da5040 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 1 Jul 2021 10:59:06 +0800 Subject: [PATCH 1199/1621] Move special case handling back to `moveSliderIntoPlayfield` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 31 +++++++++------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 844d8d76a1..ede929bfc4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -148,14 +148,20 @@ namespace osu.Game.Rulesets.Osu.Mods /// The that this slider has been shifted by. private Vector2 moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) { - var minMargin = getSliderBoundingBox(slider); + var boundingBox = getSliderBoundingBox(slider); var prevPosition = slider.Position; - slider.Position = new Vector2( - Math.Clamp(slider.Position.X, minMargin.Left, minMargin.Right), - Math.Clamp(slider.Position.Y, minMargin.Top, minMargin.Bottom) - ); + // If the slider is larger than the playfield, force it to stay at the original position + var newX = boundingBox.Width < 0 + ? currentObjectInfo.PositionOriginal.X + : Math.Clamp(slider.Position.X, boundingBox.Left, boundingBox.Right); + + var newY = boundingBox.Height < 0 + ? currentObjectInfo.PositionOriginal.Y + : Math.Clamp(slider.Position.Y, boundingBox.Top, boundingBox.Bottom); + + slider.Position = new Vector2(newX, newY); currentObjectInfo.PositionRandomised = slider.Position; currentObjectInfo.EndPositionRandomised = slider.EndPosition; @@ -192,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Mods var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); - var box = new RectangleF(); + var box = new RectangleF(Vector2.Zero, OsuPlayfield.BASE_SIZE); foreach (var pos in pathPositions) { @@ -209,19 +215,6 @@ namespace osu.Game.Rulesets.Osu.Mods box.Width -= radius * 2; box.Height -= radius * 2; - // If the slider is larger than the playfield, force the slider to stay at its original position - if (box.Width < 0) - { - box.Width = 0; - box.X = slider.Position.X; - } - - if (box.Height < 0) - { - box.Height = 0; - box.Y = slider.Position.Y; - } - return box; } From c69455cfd019d47e67f22538c2f9a83a5f0713b6 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 1 Jul 2021 11:20:55 +0800 Subject: [PATCH 1200/1621] Fixed slider bounding box calculation --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index ede929bfc4..abc7db0a82 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -202,8 +202,16 @@ namespace osu.Game.Rulesets.Osu.Mods foreach (var pos in pathPositions) { + // Reduce Width and Height accordingly after increasing X and Y + // to keep the right and bottom edge of the rectangle in place + var right = box.Right; box.X = Math.Max(box.X, -pos.X); + box.Width = right - box.X; + + var bottom = box.Bottom; box.Y = Math.Max(box.Y, -pos.Y); + box.Height = bottom - box.Y; + box.Width = Math.Min(box.Width, OsuPlayfield.BASE_SIZE.X - pos.X - box.X); box.Height = Math.Min(box.Height, OsuPlayfield.BASE_SIZE.Y - pos.Y - box.Y); } From 58f80abe322559102ea1c47c5637057cf8aa8ab8 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 1 Jul 2021 11:57:57 +0800 Subject: [PATCH 1201/1621] Several requested changes - Rename `origHitObjects` to `originalHitObjects` - Use `Value` instead of `GetValueOrDefault()` - Remove `endObj` - Added comments - Rename `closestIdx` to `precedingIndex` - Changed an `almostEquals` call --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 47 +++++++++++++--------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 12daa44477..60ca25a721 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Mods private ControlPointInfo controlPointInfo; - private List origHitObjects; + private List originalHitObjects; private Random rng; @@ -168,14 +168,14 @@ namespace osu.Game.Rulesets.Osu.Mods public override void ApplyToBeatmap(IBeatmap beatmap) { Seed.Value ??= RNG.Next(); - rng = new Random(Seed.Value.GetValueOrDefault()); + rng = new Random(Seed.Value.Value); var osuBeatmap = (OsuBeatmap)beatmap; if (osuBeatmap.HitObjects.Count == 0) return; controlPointInfo = osuBeatmap.ControlPointInfo; - origHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); + originalHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); var hitObjects = generateBeats(osuBeatmap) .Select(beat => @@ -199,9 +199,8 @@ namespace osu.Game.Rulesets.Osu.Mods private IEnumerable generateBeats(IBeatmap beatmap) { - var startTime = origHitObjects.First().StartTime; - var endObj = origHitObjects.Last(); - var endTime = endObj.GetEndTime(); + var startTime = originalHitObjects.First().StartTime; + var endTime = originalHitObjects.Last().GetEndTime(); var beats = beatmap.ControlPointInfo.TimingPoints // Ignore timing points after endTime @@ -230,24 +229,25 @@ namespace osu.Game.Rulesets.Osu.Mods { foreach (var obj in hitObjects) { - var samples = getSamplesAtTime(origHitObjects, obj.StartTime); + var samples = getSamplesAtTime(originalHitObjects, obj.StartTime); // If samples aren't available at the exact start time of the object, // use samples (without additions) in the closest original hit object instead - obj.Samples = samples ?? getClosestHitObject(origHitObjects, obj.StartTime).Samples.Where(s => !HitSampleInfo.AllAdditions.Contains(s.Name)).ToList(); + obj.Samples = samples ?? getClosestHitObject(originalHitObjects, obj.StartTime).Samples.Where(s => !HitSampleInfo.AllAdditions.Contains(s.Name)).ToList(); } } private void fixComboInfo(List hitObjects) { - // Copy combo indices from the closest preceding object - hitObjects.ForEach(obj => + // Copy combo indices from an original object at the same time or from the closest preceding object + // (Objects lying between two combos are assumed to belong to the preceding combo) + hitObjects.ForEach(newObj => { - var closestOrigObj = origHitObjects.FindLast(y => almostBigger(obj.StartTime, y.StartTime)); + var closestOrigObj = originalHitObjects.FindLast(y => almostBigger(newObj.StartTime, y.StartTime)); // It shouldn't be possible for closestOrigObj to be null // But if it is, obj should be in the first combo - obj.ComboIndex = closestOrigObj?.ComboIndex ?? 0; + newObj.ComboIndex = closestOrigObj?.ComboIndex ?? 0; }); // The copied combo indices may not be continuous if the original map starts and ends a combo in between beats @@ -383,7 +383,7 @@ namespace osu.Game.Rulesets.Osu.Mods { return breaks.Any(breakPeriod => { - var firstObjAfterBreak = origHitObjects.First(obj => almostBigger(obj.StartTime, breakPeriod.EndTime)); + var firstObjAfterBreak = originalHitObjects.First(obj => almostBigger(obj.StartTime, breakPeriod.EndTime)); return almostBigger(time, breakPeriod.StartTime) && almostBigger(firstObjAfterBreak.StartTime, time); @@ -408,14 +408,14 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuHitObject getClosestHitObject(List hitObjects, double time) { - var closestIdx = hitObjects.FindLastIndex(h => h.StartTime < time); + var precedingIndex = hitObjects.FindLastIndex(h => h.StartTime < time); - if (closestIdx == hitObjects.Count - 1) return hitObjects[closestIdx]; + if (precedingIndex == hitObjects.Count - 1) return hitObjects[precedingIndex]; // return the closest preceding/succeeding hit object, whoever is closer in time - return hitObjects[closestIdx + 1].StartTime - time < time - hitObjects[closestIdx].StartTime - ? hitObjects[closestIdx + 1] - : hitObjects[closestIdx]; + return hitObjects[precedingIndex + 1].StartTime - time < time - hitObjects[precedingIndex].StartTime + ? hitObjects[precedingIndex + 1] + : hitObjects[precedingIndex]; } /// @@ -468,7 +468,7 @@ namespace osu.Game.Rulesets.Osu.Mods double spanDuration = curve.Duration / curve.SpanCount(); double nodeIndex = timeSinceStart / spanDuration; - if (almostEquals(nodeIndex - Math.Round(nodeIndex), 0)) + if (almostEquals(nodeIndex, Math.Round(nodeIndex))) return (int)Math.Round(nodeIndex); return -1; @@ -501,6 +501,15 @@ namespace osu.Game.Rulesets.Osu.Mods obj.Position = position; } + /// + /// Re-maps a number from one range to another. + /// + /// The number to be re-mapped. + /// Beginning of the original range. + /// End of the original range. + /// Beginning of the new range. + /// End of the new range. + /// The re-mapped number. private static float map(float value, float fromLow, float fromHigh, float toLow, float toHigh) { return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow; From 34be437d7a6b9af4d71581e4b67481883e78fdcf Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 1 Jul 2021 12:21:41 +0800 Subject: [PATCH 1202/1621] Added `definitelyBigger` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 60ca25a721..a39dd7a90a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Mods var beats = beatmap.ControlPointInfo.TimingPoints // Ignore timing points after endTime - .Where(timingPoint => almostBigger(endTime, timingPoint.Time)) + .Where(timingPoint => !definitelyBigger(timingPoint.Time, endTime)) // Generate the beats .SelectMany(timingPoint => getBeatsForTimingPoint(timingPoint, endTime)) // Remove beats before startTime @@ -218,7 +218,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var beat = beats[i]; - if (almostBigger(beatmap.ControlPointInfo.TimingPointAt(beat).BeatLength / 2, beats[i + 1] - beat)) + if (!definitelyBigger(beats[i + 1] - beat, beatmap.ControlPointInfo.TimingPointAt(beat).BeatLength / 2)) beats.RemoveAt(i); } @@ -386,7 +386,7 @@ namespace osu.Game.Rulesets.Osu.Mods var firstObjAfterBreak = originalHitObjects.First(obj => almostBigger(obj.StartTime, breakPeriod.EndTime)); return almostBigger(time, breakPeriod.StartTime) - && almostBigger(firstObjAfterBreak.StartTime, time); + && definitelyBigger(firstObjAfterBreak.StartTime, time); }); } @@ -396,7 +396,7 @@ namespace osu.Game.Rulesets.Osu.Mods int i = 0; var currentTime = timingPoint.Time; - while (almostBigger(mapEndTime, currentTime) && controlPointInfo.TimingPointAt(currentTime) == timingPoint) + while (!definitelyBigger(currentTime, mapEndTime) && controlPointInfo.TimingPointAt(currentTime) == timingPoint) { beats.Add(Math.Floor(currentTime)); i++; @@ -439,6 +439,8 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(hitObject is IHasRepeats s)) return false; + // If time is outside the duration of the IHasRepeats, + // then this hitObject isn't the one we want if (!almostBigger(time, hitObject.StartTime) || !almostBigger(s.EndTime, time)) return false; @@ -520,6 +522,11 @@ namespace osu.Game.Rulesets.Osu.Mods return Precision.AlmostBigger(value1, value2, timing_precision); } + private static bool definitelyBigger(double value1, double value2) + { + return Precision.DefinitelyBigger(value1, value2, timing_precision); + } + private static bool almostEquals(double value1, double value2) { return Precision.AlmostEquals(value1, value2, timing_precision); From b0a619bb4cb0837265af38dcb86a597617fcb777 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 1 Jul 2021 12:49:34 +0800 Subject: [PATCH 1203/1621] Prevent multiple enumeration in `checkForOverlap` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index a39dd7a90a..cc3811c9b8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -300,6 +300,9 @@ namespace osu.Game.Rulesets.Osu.Mods var tryCount = 0; + // for checking overlap + var precedingObjects = hitObjects.SkipLast(hitObjects.Count - i).TakeLast(overlap_check_count).ToList(); + do { if (tryCount > 0) direction = two_pi * nextSingle(); @@ -320,7 +323,7 @@ namespace osu.Game.Rulesets.Osu.Mods tryCount++; if (tryCount % 10 == 0) distance *= 0.9f; - } while (distance >= obj.Radius * 2 && checkForOverlap(hitObjects.SkipLast(hitObjects.Count - i).TakeLast(overlap_check_count), obj)); + } while (distance >= obj.Radius * 2 && checkForOverlap(precedingObjects, obj)); if (obj.LastInCombo) direction = two_pi * nextSingle(); From 719852435f1bc52381f6e4e6ca03e97b6e05460e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 15:10:19 +0900 Subject: [PATCH 1204/1621] Fix intermittent `PerformFromScreen` test failures due to incorrect screen sequence These tests were manually pushing the `PlayerLoader` / `Player` instances to `SongSelect`, which bypasses safeties in place which avoid the exact issue that came up in https://github.com/ppy/osu/runs/2951759236 (see `AllowSelection` flag specifically). --- .../Visual/Navigation/TestScenePerformFromScreen.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 3cedaf9d45..92152bce18 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -11,6 +11,7 @@ using osu.Game.Overlays; using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps.IO; using osuTK.Input; using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation; @@ -57,8 +58,11 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestPerformAtSongSelectFromPlayerLoader() { + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); PushAndConfirm(() => new TestPlaySongSelect()); - PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer())); + + AddStep("Press enter", () => InputManager.Key(Key.Enter)); + AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader); AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) })); AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect); @@ -68,8 +72,11 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestPerformAtMenuFromPlayerLoader() { + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); PushAndConfirm(() => new TestPlaySongSelect()); - PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer())); + + AddStep("Press enter", () => InputManager.Key(Key.Enter)); + AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader); AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu); From 5bc970af0d5d26f655391ed43275e9869dbc15c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 16:55:33 +0900 Subject: [PATCH 1205/1621] Make `TestPlayer` inherit from `SoloPlayer` for more flexibility in testing --- osu.Game/Screens/Play/SoloPlayer.cs | 10 ++++++++++ osu.Game/Tests/Visual/TestPlayer.cs | 23 ++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index d0ef4131dc..0099b4c87c 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -12,6 +12,16 @@ namespace osu.Game.Screens.Play { public class SoloPlayer : SubmittingPlayer { + public SoloPlayer() + : this(null) + { + } + + protected SoloPlayer(PlayerConfiguration configuration = null) + : base(configuration) + { + } + protected override APIRequest CreateTokenRequest() { if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 09da4db952..52eb06bdc6 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -1,14 +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 System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual @@ -16,7 +20,7 @@ namespace osu.Game.Tests.Visual /// /// A player that exposes many components that would otherwise not be available, for testing purposes. /// - public class TestPlayer : Player + public class TestPlayer : SoloPlayer { protected override bool PauseOnFocusLost { get; } @@ -35,6 +39,9 @@ namespace osu.Game.Tests.Visual public new HealthProcessor HealthProcessor => base.HealthProcessor; + public bool TokenCreationRequested { get; private set; } + public bool SubmissionRequested { get; private set; } + public new bool PauseCooldownActive => base.PauseCooldownActive; public readonly List Results = new List(); @@ -49,6 +56,20 @@ namespace osu.Game.Tests.Visual PauseOnFocusLost = pauseOnFocusLost; } + protected override bool HandleTokenRetrievalFailure(Exception exception) => false; + + protected override APIRequest CreateTokenRequest() + { + TokenCreationRequested = true; + return base.CreateTokenRequest(); + } + + protected override APIRequest CreateSubmissionRequest(Score score, long token) + { + SubmissionRequested = true; + return base.CreateSubmissionRequest(score, token); + } + protected override void PrepareReplay() { // Generally, replay generation is handled by whatever is constructing the player. From 397d2491b3e2b1f74b99bab0549c17ba96a2664d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 16:55:44 +0900 Subject: [PATCH 1206/1621] Update test scenes to actually cover submission logic --- .../TestScenePlayerScorePreparation.cs | 69 ----------------- .../TestScenePlayerScoreSubmission.cs | 74 +++++++++++++++++++ 2 files changed, 74 insertions(+), 69 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs create mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs deleted file mode 100644 index 8ea5f3f9ee..0000000000 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScorePreparation.cs +++ /dev/null @@ -1,69 +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.Threading.Tasks; -using NUnit.Framework; -using osu.Framework.Screens; -using osu.Framework.Testing; -using osu.Game.Rulesets; -using osu.Game.Scoring; -using osu.Game.Screens.Ranking; - -namespace osu.Game.Tests.Visual.Gameplay -{ - public class TestScenePlayerScorePreparation : OsuPlayerTestScene - { - protected override bool AllowFail => false; - - protected new PreparingPlayer Player => (PreparingPlayer)base.Player; - - [SetUpSteps] - public override void SetUpSteps() - { - base.SetUpSteps(); - - // Ensure track has actually running before attempting to seek - AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); - } - - [Test] - public void TestPreparationOnResults() - { - AddUntilStep("wait for preparation", () => Player.PreparationCompleted); - } - - [Test] - public void TestPreparationOnExit() - { - AddStep("exit", () => Player.Exit()); - AddUntilStep("wait for preparation", () => Player.PreparationCompleted); - } - - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new PreparingPlayer(); - - public class PreparingPlayer : TestPlayer - { - public bool PreparationCompleted { get; private set; } - - public bool ResultsCreated { get; private set; } - - public PreparingPlayer() - : base(true, true) - { - } - - protected override ResultsScreen CreateResults(ScoreInfo score) - { - var results = base.CreateResults(score); - ResultsCreated = true; - return results; - } - - protected override Task PrepareScoreForResultsAsync(Score score) - { - PreparationCompleted = true; - return base.PrepareScoreForResultsAsync(score); - } - } - } -} diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs new file mode 100644 index 0000000000..24f8227148 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.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 System.Linq; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Online.Solo; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestScenePlayerScoreSubmission : OsuPlayerTestScene + { + protected override bool AllowFail => false; + + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + protected override TestPlayer CreatePlayer(Ruleset ruleset) + { + SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().First() }; + return new TestPlayer(false); + } + + [SetUpSteps] + public override void SetUpSteps() + { + AddStep("Prepare test API", () => + { + dummyAPI.HandleRequest = request => + { + switch (request) + { + case CreateSoloScoreRequest tokenRequest: + tokenRequest.TriggerSuccess(new APIScoreToken { ID = 1234 }); + return true; + } + + return false; + }; + }); + + base.SetUpSteps(); + + // Ensure track has actually running before attempting to seek + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + } + + [Test] + public void TestSubmissionOnResults() + { + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + + AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); + + AddUntilStep("wait for submission", () => Player.SubmissionRequested); + } + + [Test] + public void TestSubmissionOnExit() + { + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + AddStep("exit", () => Player.Exit()); + AddUntilStep("wait for submission", () => Player.SubmissionRequested); + } + } +} From 04b874bb009a85b67c5783f30aa4a6f2bfb6862a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 17:02:33 +0900 Subject: [PATCH 1207/1621] Add flow for submitting score on exiting `SubmittingPlayer` --- osu.Game/Screens/Play/SubmittingPlayer.cs | 60 ++++++++++++++--------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 2ee116a6a3..bbb6a21f39 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -27,6 +27,8 @@ namespace osu.Game.Screens.Play [Resolved] private IAPIProvider api { get; set; } + private TaskCompletionSource scoreSubmissionSource; + protected SubmittingPlayer(PlayerConfiguration configuration = null) : base(configuration) { @@ -102,36 +104,18 @@ namespace osu.Game.Screens.Play /// Whether gameplay should be immediately exited as a result. Returning false allows the gameplay session to continue. Defaults to true. protected virtual bool HandleTokenRetrievalFailure(Exception exception) => true; - public override bool OnExiting(IScreen next) - { - return base.OnExiting(next); - } - protected override async Task PrepareScoreForResultsAsync(Score score) { await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); - // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure). - if (token == null) - return; + await submitScore(score).ConfigureAwait(false); + } - var tcs = new TaskCompletionSource(); - var request = CreateSubmissionRequest(score, token.Value); + public override bool OnExiting(IScreen next) + { + submitScore(Score); - request.Success += s => - { - score.ScoreInfo.OnlineScoreID = s.ID; - tcs.SetResult(true); - }; - - request.Failure += e => - { - Logger.Error(e, "Failed to submit score"); - tcs.SetResult(false); - }; - - api.Queue(request); - await tcs.Task.ConfigureAwait(false); + return base.OnExiting(next); } /// @@ -148,5 +132,33 @@ namespace osu.Game.Screens.Play /// The score to be submitted. /// The submission token. protected abstract APIRequest CreateSubmissionRequest(Score score, long token); + + private Task submitScore(Score score) + { + // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure). + if (token == null) + return Task.CompletedTask; + + if (scoreSubmissionSource != null) + return scoreSubmissionSource.Task; + + scoreSubmissionSource = new TaskCompletionSource(); + var request = CreateSubmissionRequest(score, token.Value); + + request.Success += s => + { + score.ScoreInfo.OnlineScoreID = s.ID; + scoreSubmissionSource.SetResult(true); + }; + + request.Failure += e => + { + Logger.Error(e, "Failed to submit score"); + scoreSubmissionSource.SetResult(false); + }; + + api.Queue(request); + return scoreSubmissionSource.Task; + } } } From 6e8d4e382ebf3597181cee171b20ed175dab9fc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 17:20:40 +0900 Subject: [PATCH 1208/1621] Add test coverage of token failure scenarios --- .../TestScenePlayerScoreSubmission.cs | 96 +++++++++++++------ 1 file changed, 67 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 24f8227148..4088fc855d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -1,10 +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.Linq; using NUnit.Framework; using osu.Framework.Screens; -using osu.Framework.Testing; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; @@ -21,14 +21,74 @@ namespace osu.Game.Tests.Visual.Gameplay private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + protected override bool HasCustomSteps => true; + protected override TestPlayer CreatePlayer(Ruleset ruleset) { SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().First() }; return new TestPlayer(false); } - [SetUpSteps] - public override void SetUpSteps() + [Test] + public void TestNoSubmissionOnResultsWithNoToken() + { + prepareTokenResponse(false); + + CreateTest(() => { }); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + + AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); + + AddAssert("ensure no submission", () => !Player.SubmissionRequested); + } + + [Test] + public void TestSubmissionOnResults() + { + prepareTokenResponse(true); + + CreateTest(() => { }); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + + AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); + + AddAssert("ensure submission", () => Player.SubmissionRequested); + } + + [Test] + public void TestNoSubmissionOnExitWithNoToken() + { + prepareTokenResponse(false); + + CreateTest(() => { }); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + AddStep("exit", () => Player.Exit()); + + AddAssert("ensure no submission", () => !Player.SubmissionRequested); + } + + [Test] + public void TestSubmissionOnExit() + { + prepareTokenResponse(true); + + CreateTest(() => { }); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + AddStep("exit", () => Player.Exit()); + AddUntilStep("wait for submission", () => Player.SubmissionRequested); + } + + private void prepareTokenResponse(bool validToken) { AddStep("Prepare test API", () => { @@ -37,38 +97,16 @@ namespace osu.Game.Tests.Visual.Gameplay switch (request) { case CreateSoloScoreRequest tokenRequest: - tokenRequest.TriggerSuccess(new APIScoreToken { ID = 1234 }); + if (validToken) + tokenRequest.TriggerSuccess(new APIScoreToken { ID = 1234 }); + else + tokenRequest.TriggerFailure(new Exception()); return true; } return false; }; }); - - base.SetUpSteps(); - - // Ensure track has actually running before attempting to seek - AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); - } - - [Test] - public void TestSubmissionOnResults() - { - AddUntilStep("wait for token request", () => Player.TokenCreationRequested); - - AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); - - AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); - - AddUntilStep("wait for submission", () => Player.SubmissionRequested); - } - - [Test] - public void TestSubmissionOnExit() - { - AddUntilStep("wait for token request", () => Player.TokenCreationRequested); - AddStep("exit", () => Player.Exit()); - AddUntilStep("wait for submission", () => Player.SubmissionRequested); } } } From b00ee67895cba2bdbe64c49c9b698f87413bb2fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 17:25:47 +0900 Subject: [PATCH 1209/1621] Remove excess whitespace --- osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index ce4ec27225..056d4ad6f7 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Dashboard switch (value) { case DashboardOverlayTabs.Friends: - return FriendsStrings.TitleCompact; + return FriendsStrings.TitleCompact; case DashboardOverlayTabs.CurrentlyPlaying: return @"Currently Playing"; From 74c63e15bef6d50aa8e4a43a44a7f8a449d0984c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 17:38:28 +0900 Subject: [PATCH 1210/1621] Mark score failed on fail and exit --- .../TestScenePlayerScoreSubmission.cs | 38 ++++++++++++++----- osu.Game/Screens/Play/Player.cs | 5 +++ osu.Game/Screens/Play/SubmittingPlayer.cs | 4 +- osu.Game/Tests/Visual/TestPlayer.cs | 5 ++- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 4088fc855d..ada50fbcf4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -23,9 +23,14 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool HasCustomSteps => true; + private bool allowFail; + protected override TestPlayer CreatePlayer(Ruleset ruleset) { - SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().First() }; + SelectedMods.Value = !allowFail + ? new[] { ruleset.GetAllMods().OfType().First() } + : Array.Empty(); + return new TestPlayer(false); } @@ -34,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(false); - CreateTest(() => { }); + CreateTest(() => allowFail = false); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -43,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); - AddAssert("ensure no submission", () => !Player.SubmissionRequested); + AddAssert("ensure no submission", () => Player.SubmittedScore == null); } [Test] @@ -51,7 +56,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => { }); + CreateTest(() => allowFail = false); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -59,8 +64,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); - - AddAssert("ensure submission", () => Player.SubmissionRequested); + AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true); } [Test] @@ -68,12 +72,26 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(false); - CreateTest(() => { }); + CreateTest(() => allowFail = false); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddStep("exit", () => Player.Exit()); + AddAssert("ensure no submission", () => Player.SubmittedScore == null); + } + + [Test] + public void TestSubmissionOnFail() + { + prepareTokenResponse(true); + + CreateTest(() => allowFail = true); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + AddUntilStep("wait for fail", () => Player.HasFailed); AddStep("exit", () => Player.Exit()); - AddAssert("ensure no submission", () => !Player.SubmissionRequested); + AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); } [Test] @@ -81,11 +99,11 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => { }); + CreateTest(() => allowFail = false); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); AddStep("exit", () => Player.Exit()); - AddUntilStep("wait for submission", () => Player.SubmissionRequested); + AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); } private void prepareTokenResponse(bool validToken) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 58f60d14cf..97854ee12f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -768,6 +768,7 @@ namespace osu.Game.Screens.Play return false; HasFailed = true; + Score.ScoreInfo.Passed = false; // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) // could process an extra frame after the GameplayClock is stopped. @@ -950,6 +951,10 @@ namespace osu.Game.Screens.Play { screenSuspension?.Expire(); + // if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap. + if (prepareScoreForDisplayTask == null) + Score.ScoreInfo.Passed = false; + // 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. diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index bbb6a21f39..7c5a06707d 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -113,9 +113,11 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { + var exiting = base.OnExiting(next); + submitScore(Score); - return base.OnExiting(next); + return exiting; } /// diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 52eb06bdc6..5e5f20b307 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -40,7 +40,8 @@ namespace osu.Game.Tests.Visual public new HealthProcessor HealthProcessor => base.HealthProcessor; public bool TokenCreationRequested { get; private set; } - public bool SubmissionRequested { get; private set; } + + public Score SubmittedScore { get; private set; } public new bool PauseCooldownActive => base.PauseCooldownActive; @@ -66,7 +67,7 @@ namespace osu.Game.Tests.Visual protected override APIRequest CreateSubmissionRequest(Score score, long token) { - SubmissionRequested = true; + SubmittedScore = score; return base.CreateSubmissionRequest(score, token); } From a6323b7d87fba2a0adad4a750a9155fcde16bf49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 17:54:59 +0900 Subject: [PATCH 1211/1621] Use `APIException` --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index ada50fbcf4..27f9fc85cf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Gameplay if (validToken) tokenRequest.TriggerSuccess(new APIScoreToken { ID = 1234 }); else - tokenRequest.TriggerFailure(new Exception()); + tokenRequest.TriggerFailure(new APIException("something went wrong!", null)); return true; } From 3816c486d5fb5f13c226f8b1cd72f457cef4a51c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 19:03:55 +0900 Subject: [PATCH 1212/1621] Guard against a potential startup crash if user's preferred ruleset has a compatibility issue Resolves this issue seen at https://github.com/ppy/osu/issues/13722#issuecomment-872088071. --- osu.Game/OsuGame.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 32136b8789..14309e2296 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -223,7 +223,20 @@ namespace osu.Game // bind config int to database RulesetInfo configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); - Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); + + var preferredRuleset = RulesetStore.GetRuleset(configRuleset.Value); + + try + { + Ruleset.Value = preferredRuleset ?? RulesetStore.AvailableRulesets.First(); + } + catch (Exception e) + { + // on startup, a ruleset may be selected which has compatibility issues. + Logger.Error(e, $@"Failed to switch to preferred ruleset {preferredRuleset}."); + Ruleset.Value = RulesetStore.AvailableRulesets.First(); + } + Ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ID ?? 0; // bind config int to database SkinInfo From a78d1b4c2ee283e8e83f9013bc4bacd762bbacea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Jul 2021 19:37:21 +0900 Subject: [PATCH 1213/1621] Update SR colours to match osu-web --- osu.Game/Graphics/OsuColour.cs | 4 ++-- osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 15967c37c2..a44c28eaa6 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -32,10 +32,10 @@ namespace osu.Game.Graphics return Pink; case DifficultyRating.Expert: - return useLighterColour ? PurpleLight : Purple; + return PurpleLight; case DifficultyRating.ExpertPlus: - return useLighterColour ? Gray9 : Gray0; + return useLighterColour ? Gray9 : Color4Extensions.FromHex("#121415"); } } diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index 7aba699216..e59a0de316 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -4,9 +4,7 @@ using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; @@ -111,12 +109,9 @@ namespace osu.Game.Screens.Ranking.Expanded var rating = Current.Value.DifficultyRating; - background.Colour = rating == DifficultyRating.ExpertPlus - ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(rating); + background.Colour = colours.ForDifficultyRating(rating, true); textFlow.Clear(); - textFlow.AddText($"{wholePart}", s => { s.Colour = Color4.Black; From 0522500a572a8125529ad399cc628fc558b407b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 19:45:17 +0900 Subject: [PATCH 1214/1621] Fix a couple of merge oversights --- osu.Game/Overlays/BeatmapListingOverlay.cs | 6 +++++- osu.Game/Screens/Select/BeatmapDetails.cs | 3 --- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 4552c8d11b..6861d17f26 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -89,7 +89,11 @@ namespace osu.Game.Overlays }; } - public void ShowWithSearch(string query) => filterControl.Search(query); + public void ShowWithSearch(string query) + { + filterControl.Search(query); + Show(); + } protected override BeatmapListingHeader CreateHeader() => new BeatmapListingHeader(); diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 38478ce461..d7dc73cb37 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -149,9 +149,6 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - description = new MetadataSection(MetadataType.Description), - source = new MetadataSection(MetadataType.Source), - tags = new MetadataSection(MetadataType.Tags), new OsuSpriteText { Text = "Points of Failure", From 4c1b8bc42726a9cd352a57388ffdd3c15dee99ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 20:23:12 +0900 Subject: [PATCH 1215/1621] Update disclaimer --- osu.Game/Screens/Menu/Disclaimer.cs | 63 ++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 72eb9c7c0c..78dda05a19 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.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 osu.Framework.Allocation; using osu.Framework.Bindables; @@ -36,6 +37,8 @@ namespace osu.Game.Screens.Menu private readonly Bindable currentUser = new Bindable(); private FillFlowContainer fill; + private readonly List expendableText = new List(); + public Disclaimer(OsuScreen nextScreen = null) { this.nextScreen = nextScreen; @@ -54,7 +57,7 @@ namespace osu.Game.Screens.Menu { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.Solid.Flask, + Icon = OsuIcon.Logo, Size = new Vector2(icon_size), Y = icon_y, }, @@ -76,37 +79,53 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Spacing = new Vector2(0, 2), - LayoutDuration = 2000, - LayoutEasing = Easing.OutQuint - }, - supportFlow = new LinkFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Alpha = 0, - Spacing = new Vector2(0, 2), }, } - } + }, + supportFlow = new LinkFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Padding = new MarginPadding(20), + Alpha = 0, + Spacing = new Vector2(0, 2), + }, }; - textFlow.AddText("This project is an ongoing ", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Light)); - textFlow.AddText("work in progress", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.SemiBold)); + textFlow.AddText("This is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular)); + + expendableText.AddRange(textFlow.AddText("(lazer)", t => + { + t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular); + t.Colour = colours.PinkLight; + })); textFlow.NewParagraph(); - static void format(SpriteText t) => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold); + textFlow.AddText("the next ", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); + textFlow.AddText("major update", t => + { + t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold); + t.Colour = colours.Pink; + }); + expendableText.AddRange(textFlow.AddText(" coming to osu!", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular))); + textFlow.AddText(".", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); - textFlow.AddParagraph(getRandomTip(), t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold)); + textFlow.NewParagraph(); + + textFlow.AddParagraph("Today's Tip: ", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold)); + textFlow.AddText(getRandomTip(), t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); textFlow.NewParagraph(); textFlow.NewParagraph(); iconColour = colours.Yellow; + static void format(SpriteText t) => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold); + // manually transfer the user once, but only do the final bind in LoadComplete to avoid thread woes (API scheduler could run while this screen is still loading). // the manual transfer is here to ensure all text content is loaded ahead of time as this is very early in the game load process and we want to avoid stutters. currentUser.Value = api.LocalUser.Value; @@ -122,7 +141,7 @@ namespace osu.Game.Screens.Menu { supportFlow.AddText("Consider becoming an ", format); supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format); - supportFlow.AddText(" to help support the game", format); + supportFlow.AddText(" to help support osu!'s development", format); } heart = supportFlow.AddIcon(FontAwesome.Solid.Heart, t => @@ -169,7 +188,11 @@ namespace osu.Game.Screens.Menu .MoveToY(icon_y, 160, Easing.InQuart) .FadeColour(Color4.White, 160); - fill.Delay(520 + 160).MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart); + using (BeginDelayedSequence(520 + 160)) + { + fill.MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart); + Schedule(() => expendableText.ForEach(t => t.Expire())); + } } supportFlow.FadeOut().Delay(2000).FadeIn(500); From 4c95af4b16a961743639de95451104cd604b8de8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Jul 2021 20:35:31 +0900 Subject: [PATCH 1216/1621] Add star rating range display --- .../TestSceneStarRatingRangeDisplay.cs | 40 ++++++++ .../Components/StarRatingRangeDisplay.cs | 93 +++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs create mode 100644 osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs new file mode 100644 index 0000000000..cdeafdc9a3 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.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 NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneStarRatingRangeDisplay : OnlinePlayTestScene + { + [SetUp] + public new void Setup() => Schedule(() => + { + SelectedRoom.Value = new Room(); + + Child = new StarRatingRangeDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + }); + + [Test] + public void TestRange([Values(0, 2, 3, 4, 6, 7)] double min, [Values(0, 2, 3, 4, 6, 7)] double max) + { + AddStep("set playlist", () => + { + SelectedRoom.Value.Playlist.AddRange(new[] + { + new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = min } } }, + new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = max } } }, + }); + }); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs new file mode 100644 index 0000000000..b2e35d7020 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -0,0 +1,93 @@ +// 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.Specialized; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Screens.Ranking.Expanded; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Components +{ + public class StarRatingRangeDisplay : OnlinePlayComposite + { + [Resolved] + private OsuColour colours { get; set; } + + private StarRatingDisplay minDisplay; + private Drawable minBackground; + private StarRatingDisplay maxDisplay; + private Drawable maxBackground; + + public StarRatingRangeDisplay() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 1, + Children = new[] + { + minBackground = new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f), + }, + maxBackground = new Box + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f), + }, + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + minDisplay = new StarRatingDisplay(default), + maxDisplay = new StarRatingDisplay(default) + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Playlist.BindCollectionChanged(updateRange, true); + } + + private void updateRange(object sender, NotifyCollectionChangedEventArgs e) + { + var orderedDifficulties = Playlist.Select(p => p.Beatmap.Value).OrderBy(b => b.StarDifficulty).ToArray(); + + StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarDifficulty : 0, 0); + StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarDifficulty : 0, 0); + + minDisplay.Current.Value = minDifficulty; + maxDisplay.Current.Value = maxDifficulty; + + minBackground.Colour = colours.ForDifficultyRating(minDifficulty.DifficultyRating, true); + maxBackground.Colour = colours.ForDifficultyRating(maxDifficulty.DifficultyRating, true); + } + } +} From 68c5e6a4311080716624c1114e1b63a90c2dd74f Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 1 Jul 2021 20:41:30 +0900 Subject: [PATCH 1217/1621] Add audio feedback to changing volume --- osu.Game/Overlays/Volume/VolumeMeter.cs | 35 ++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index a15076581e..2251be03c6 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -4,6 +4,8 @@ using System; using System.Globalization; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -36,6 +38,10 @@ namespace osu.Game.Overlays.Volume private OsuSpriteText text; private BufferedContainer maxGlow; + private bool firstUpdate = true; + private Sample sample; + private double sampleLastPlaybackTime; + public VolumeMeter(string name, float circleSize, Color4 meterColour) { this.circleSize = circleSize; @@ -46,8 +52,11 @@ namespace osu.Game.Overlays.Volume } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, AudioManager audio) { + sample = audio.Samples.Get(@"UI/notch-tick"); + sampleLastPlaybackTime = Time.Current; + Color4 backgroundColour = colours.Gray1; CircularProgress bgProgress; @@ -202,6 +211,7 @@ namespace osu.Game.Overlays.Volume get => displayVolume; set { + bool displayVolumeChanged = Math.Round(displayVolume * 100) != Math.Round(value * 100); displayVolume = value; if (displayVolume >= 0.995f) @@ -217,6 +227,29 @@ namespace osu.Game.Overlays.Volume volumeCircle.Current.Value = displayVolume * 0.75f; volumeCircleGlow.Current.Value = displayVolume * 0.75f; + + Schedule(() => + { + const int sfx_debounce_time = 30; + + if (firstUpdate || + !displayVolumeChanged || + Time.Current - sampleLastPlaybackTime <= sfx_debounce_time) + { + firstUpdate = false; + return; + } + + var channel = sample.Play(); + + channel.Frequency.Value = 1 + displayVolume * 0.1f + RNG.NextDouble(0.02f); + if (displayVolume < 0.005f) + channel.Frequency.Value -= 0.5f; + else if (displayVolume > 0.995f) + channel.Frequency.Value -= 0.5f; + + sampleLastPlaybackTime = Time.Current; + }); } } From dad28b28265ddec8f6c478041b8aee4317f43c22 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 1 Jul 2021 20:45:29 +0900 Subject: [PATCH 1218/1621] Update OsuSliderBar to use new notch-tick sample and tweak pitch ramping --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index ae16169123..1433f0c38b 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Utils; namespace osu.Game.Graphics.UserInterface { @@ -99,7 +100,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) { - sample = audio.Samples.Get(@"UI/sliderbar-notch"); + sample = audio.Samples.Get(@"UI/notch-tick"); AccentColour = colours.Pink; } @@ -149,7 +150,7 @@ namespace osu.Game.Graphics.UserInterface private void playSample(T value) { - if (Clock == null || Clock.CurrentTime - lastSampleTime <= 50) + if (Clock == null || Clock.CurrentTime - lastSampleTime <= 30) return; if (value.Equals(lastSampleValue)) @@ -160,11 +161,11 @@ namespace osu.Game.Graphics.UserInterface var channel = sample.Play(); - channel.Frequency.Value = 1 + NormalizedValue * 0.2f; + channel.Frequency.Value = 1 + NormalizedValue * 0.2f + RNG.NextDouble(0.02f); if (NormalizedValue == 0) - channel.Frequency.Value -= 0.4f; + channel.Frequency.Value -= 0.5f; else if (NormalizedValue == 1) - channel.Frequency.Value += 0.4f; + channel.Frequency.Value -= 0.5f; } private void updateTooltipText(T value) From f1c11243e92be087d5977bbd5e8a10d2a19a42b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jul 2021 22:06:59 +0900 Subject: [PATCH 1219/1621] 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 481ddc118f..b2b48f5de8 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 589afb86be..42eb2ad1d8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a8bf0e4ab2..67993049f2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 8ab9ea992635622ed9e3b043e23ca7934f418ad3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:03 +0000 Subject: [PATCH 1220/1621] Bump Realm from 10.2.0 to 10.2.1 Bumps [Realm](https://github.com/realm/realm-dotnet) from 10.2.0 to 10.2.1. - [Release notes](https://github.com/realm/realm-dotnet/releases) - [Changelog](https://github.com/realm/realm-dotnet/blob/master/CHANGELOG.md) - [Commits](https://github.com/realm/realm-dotnet/compare/10.2.0...10.2.1) --- updated-dependencies: - dependency-name: Realm dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 481ddc118f..2e388b8626 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 589afb86be..24779fc4d1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index a8bf0e4ab2..33ea2261ca 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -99,6 +99,6 @@ - + From c976854e2449b8f659cebe2be22e2ec9da0bc45c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:08 +0000 Subject: [PATCH 1221/1621] Bump Sentry from 3.4.0 to 3.6.0 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 3.4.0 to 3.6.0. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Changelog](https://github.com/getsentry/sentry-dotnet/blob/main/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/3.4.0...3.6.0) --- updated-dependencies: - dependency-name: Sentry dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- 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 589afb86be..0ab6bb9ba6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -38,7 +38,7 @@ - + From e5e468e38787da508cd291a205e7ce406c34be3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:11 +0000 Subject: [PATCH 1222/1621] Bump SharpCompress from 0.28.2 to 0.28.3 Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.28.2 to 0.28.3. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.28.2...0.28.3) --- updated-dependencies: - dependency-name: SharpCompress dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 589afb86be..f857d155ec 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -39,7 +39,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a8bf0e4ab2..f1ee770688 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -94,7 +94,7 @@ - + From 71867337b6c4e00310d3232b3ca88ccaebc9f3d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:15 +0000 Subject: [PATCH 1223/1621] Bump HtmlAgilityPack from 1.11.33 to 1.11.34 Bumps [HtmlAgilityPack](https://github.com/zzzprojects/html-agility-pack) from 1.11.33 to 1.11.34. - [Release notes](https://github.com/zzzprojects/html-agility-pack/releases) - [Commits](https://github.com/zzzprojects/html-agility-pack/compare/v1.11.33...v1.11.34) --- updated-dependencies: - dependency-name: HtmlAgilityPack dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 589afb86be..f5838f5109 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,7 +20,7 @@ - + From 0d3de488de65aeb6310d924fadc67e8433b0151c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:17 +0000 Subject: [PATCH 1224/1621] Bump NUnit3TestAdapter from 3.17.0 to 4.0.0 Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 3.17.0 to 4.0.0. - [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases) - [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V3.17...V4.0.0) --- updated-dependencies: - dependency-name: NUnit3TestAdapter dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 5eb5efa54c..3dd6be7307 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index d7c116411a..0c4bfe0ed7 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 89b551286b..bb0a487274 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index d7c116411a..0c4bfe0ed7 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 7a74563b2b..da8a0540f4 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -9,7 +9,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 83d0744588..484da8e22e 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index b2a0912d19..6df555617b 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,7 @@ - + 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 1efd19f49d..68be34d153 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 @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 8fb167ba10..532fdc5cb0 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 35d3c7f202..161e248d96 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 2084be765a..ba096abd36 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe From b320528eb59b72b50a53bf33e1706f7c6a428525 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:22 +0000 Subject: [PATCH 1225/1621] Bump Humanizer from 2.10.1 to 2.11.10 Bumps [Humanizer](https://github.com/Humanizr/Humanizer) from 2.10.1 to 2.11.10. - [Release notes](https://github.com/Humanizr/Humanizer/releases) - [Changelog](https://github.com/Humanizr/Humanizer/blob/main/release_notes.md) - [Commits](https://github.com/Humanizr/Humanizer/compare/v2.10.1...v2.11.10) --- updated-dependencies: - dependency-name: Humanizer dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 589afb86be..74ed0b8299 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a8bf0e4ab2..8226d23e0a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -89,7 +89,7 @@ - + From e7c7b8512ac012bb77852603c079893a9471a147 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:25 +0000 Subject: [PATCH 1226/1621] Bump Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson Bumps [Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson](https://github.com/dotnet/aspnetcore) from 5.0.6 to 5.0.7. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.6...v5.0.7) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 589afb86be..2709f25bb4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + From fb2d08b9ee2a18501464c9c3583c71a349e969d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:29 +0000 Subject: [PATCH 1227/1621] Bump ppy.LocalisationAnalyser from 2021.608.0 to 2021.614.0 Bumps [ppy.LocalisationAnalyser](https://github.com/ppy/osu-localisation-analyser) from 2021.608.0 to 2021.614.0. - [Release notes](https://github.com/ppy/osu-localisation-analyser/releases) - [Commits](https://github.com/ppy/osu-localisation-analyser/compare/2021.608.0...2021.614.0) --- updated-dependencies: - dependency-name: ppy.LocalisationAnalyser dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- 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 589afb86be..b8502216f3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From f6234864cc8cfe11d678d2a4ed3226003ff06ff9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:33 +0000 Subject: [PATCH 1228/1621] Bump Microsoft.AspNetCore.SignalR.Client from 5.0.6 to 5.0.7 Bumps [Microsoft.AspNetCore.SignalR.Client](https://github.com/dotnet/aspnetcore) from 5.0.6 to 5.0.7. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.6...v5.0.7) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.SignalR.Client dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 589afb86be..44e7a00839 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + From d0ae946cb6d70a81e4b4b453db31fc480fa16f2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:02:38 +0000 Subject: [PATCH 1229/1621] Bump Microsoft.AspNetCore.SignalR.Protocols.MessagePack Bumps [Microsoft.AspNetCore.SignalR.Protocols.MessagePack](https://github.com/dotnet/aspnetcore) from 5.0.6 to 5.0.7. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.6...v5.0.7) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.MessagePack dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 589afb86be..470f7f48d9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + From 9b0fa6d3fcb66536b1b2b8fe62c54aa005cc773f Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Thu, 1 Jul 2021 15:38:38 -0700 Subject: [PATCH 1230/1621] Make flipping reflect across the axes of the selection box --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 23f36ffe5b..7a35c8600d 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -127,6 +127,7 @@ namespace osu.Game.Skinning.Editor public override bool HandleFlip(Direction direction) { var selectionQuad = getSelectionQuad(); + Vector2 scaleFactor = direction == Direction.Horizontal ? new Vector2(-1, 1) : new Vector2(1, -1); foreach (var b in SelectedBlueprints) { @@ -136,10 +137,8 @@ namespace osu.Game.Skinning.Editor updateDrawablePosition(drawableItem, flippedPosition); - drawableItem.Scale *= new Vector2( - direction == Direction.Horizontal ? -1 : 1, - direction == Direction.Vertical ? -1 : 1 - ); + drawableItem.Scale *= scaleFactor; + drawableItem.Rotation -= drawableItem.Rotation % 180 * 2; } return true; From 083e463afd8065f19d082e9e158ab6ad31afdae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Jul 2021 01:02:36 +0200 Subject: [PATCH 1231/1621] Use alternative hue slider nub design --- .../UserInterfaceV2/OsuHSVColourPicker.cs | 68 ++++++++++++------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs index 2a399cfaf8..06056f239b 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; @@ -15,6 +16,10 @@ namespace osu.Game.Graphics.UserInterfaceV2 { public class OsuHSVColourPicker : HSVColourPicker { + private const float spacing = 10; + private const float corner_radius = 10; + private const float control_border_thickness = 3; + protected override HueSelector CreateHueSelector() => new OsuHueSelector(); protected override SaturationValueSelector CreateSaturationValueSelector() => new OsuSaturationValueSelector(); @@ -23,37 +28,58 @@ namespace osu.Game.Graphics.UserInterfaceV2 { Background.Colour = colourProvider?.Dark5 ?? osuColour.GreySeafoamDark; - Content.Padding = new MarginPadding(10); - Content.Spacing = new Vector2(0, 10); + Content.Padding = new MarginPadding(spacing); + Content.Spacing = new Vector2(0, spacing); } + private static EdgeEffectParameters createShadowParameters() => new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0, 1), + Radius = 3, + Colour = Colour4.Black.Opacity(0.3f) + }; + private class OsuHueSelector : HueSelector { public OsuHueSelector() { - Margin = new MarginPadding - { - Bottom = 15 - }; - - SliderBar.CornerRadius = SliderBar.Height / 2; + SliderBar.CornerRadius = corner_radius; SliderBar.Masking = true; } - protected override Drawable CreateSliderNub() => new SliderNub(); + protected override Drawable CreateSliderNub() => new SliderNub(this); private class SliderNub : CompositeDrawable { - public SliderNub() + private readonly Bindable hue; + private readonly Box fill; + + public SliderNub(OsuHueSelector osuHueSelector) { - InternalChild = new Triangle + hue = osuHueSelector.Hue.GetBoundCopy(); + + InternalChild = new CircularContainer { - Width = 20, - Height = 15, - Anchor = Anchor.BottomCentre, - Origin = Anchor.TopCentre + Height = 35, + Width = 10, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Masking = true, + BorderColour = Colour4.White, + BorderThickness = control_border_thickness, + EdgeEffect = createShadowParameters(), + Child = fill = new Box + { + RelativeSizeAxes = Axes.Both + } }; } + + protected override void LoadComplete() + { + hue.BindValueChanged(h => fill.Colour = Colour4.FromHSV(h.NewValue, 1, 1), true); + } } } @@ -61,7 +87,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { public OsuSaturationValueSelector() { - SelectionArea.CornerRadius = 10; + SelectionArea.CornerRadius = corner_radius; SelectionArea.Masking = true; // purposefully use hard non-AA'd masking to avoid edge artifacts. SelectionArea.MaskingSmoothness = 0; @@ -82,14 +108,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 Size = new Vector2(20), Masking = true, BorderColour = Colour4.White, - BorderThickness = 3, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0, 1), - Radius = 3, - Colour = Colour4.Black.Opacity(0.3f) - }, + BorderThickness = control_border_thickness, + EdgeEffect = createShadowParameters(), Child = previewBox = new Box { RelativeSizeAxes = Axes.Both From a1eaf396453b770712b645d096e52e90330f571f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 12:15:16 +0900 Subject: [PATCH 1232/1621] Slightly change wording to hopefully read better --- osu.Game/Screens/Menu/Disclaimer.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 78dda05a19..6ae7142814 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -95,9 +95,9 @@ namespace osu.Game.Screens.Menu }, }; - textFlow.AddText("This is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular)); + textFlow.AddText("this is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular)); - expendableText.AddRange(textFlow.AddText("(lazer)", t => + expendableText.AddRange(textFlow.AddText("lazer", t => { t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular); t.Colour = colours.PinkLight; @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Menu textFlow.NewParagraph(); - textFlow.AddParagraph("Today's Tip: ", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold)); + textFlow.AddParagraph("today's tip: ", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold)); textFlow.AddText(getRandomTip(), t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); textFlow.NewParagraph(); @@ -191,7 +191,11 @@ namespace osu.Game.Screens.Menu using (BeginDelayedSequence(520 + 160)) { fill.MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart); - Schedule(() => expendableText.ForEach(t => t.Expire())); + Schedule(() => expendableText.ForEach(t => + { + t.FadeOut(100); + t.ScaleTo(new Vector2(0, 1), 100, Easing.OutQuart); + })); } } From f0727a63bebe1c581db6da303f34c46dd043aa57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 12:16:45 +0900 Subject: [PATCH 1233/1621] Reduce usable width --- osu.Game/Screens/Menu/Disclaimer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 6ae7142814..59f0f26b94 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Menu { textFlow = new LinkFlowContainer { - RelativeSizeAxes = Axes.X, + Width = 680, AutoSizeAxes = Axes.Y, TextAnchor = Anchor.TopCentre, Anchor = Anchor.TopCentre, From 62c125d7a2d76cab5f756a4b57336158dea4a31a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 12:21:28 +0900 Subject: [PATCH 1234/1621] Tidy up font formatting methods --- osu.Game/Screens/Menu/Disclaimer.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 59f0f26b94..b0a2fad813 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -103,29 +103,31 @@ namespace osu.Game.Screens.Menu t.Colour = colours.PinkLight; })); + static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular); + static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold); + textFlow.NewParagraph(); - textFlow.AddText("the next ", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); + textFlow.AddText("the next ", formatRegular); textFlow.AddText("major update", t => { t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold); t.Colour = colours.Pink; }); - expendableText.AddRange(textFlow.AddText(" coming to osu!", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular))); - textFlow.AddText(".", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); + expendableText.AddRange(textFlow.AddText(" coming to osu!", formatRegular)); + textFlow.AddText(".", formatRegular); + textFlow.NewParagraph(); textFlow.NewParagraph(); - textFlow.AddParagraph("today's tip: ", t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold)); - textFlow.AddText(getRandomTip(), t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.Regular)); + textFlow.AddParagraph("today's tip:", formatSemiBold); + textFlow.AddParagraph(getRandomTip(), formatRegular); textFlow.NewParagraph(); textFlow.NewParagraph(); iconColour = colours.Yellow; - static void format(SpriteText t) => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold); - // manually transfer the user once, but only do the final bind in LoadComplete to avoid thread woes (API scheduler could run while this screen is still loading). // the manual transfer is here to ensure all text content is loaded ahead of time as this is very early in the game load process and we want to avoid stutters. currentUser.Value = api.LocalUser.Value; @@ -135,19 +137,19 @@ namespace osu.Game.Screens.Menu if (e.NewValue.IsSupporter) { - supportFlow.AddText("Eternal thanks to you for supporting osu!", format); + supportFlow.AddText("Eternal thanks to you for supporting osu!", formatSemiBold); } else { - supportFlow.AddText("Consider becoming an ", format); - supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format); - supportFlow.AddText(" to help support osu!'s development", format); + supportFlow.AddText("Consider becoming an ", formatSemiBold); + supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", formatSemiBold); + supportFlow.AddText(" to help support osu!'s development", formatSemiBold); } heart = supportFlow.AddIcon(FontAwesome.Solid.Heart, t => { t.Padding = new MarginPadding { Left = 5, Top = 3 }; - t.Font = t.Font.With(size: 12); + t.Font = t.Font.With(size: 20); t.Origin = Anchor.Centre; t.Colour = colours.Pink; }).First(); From 0396a03bbc7f9e041a7d0feb10aa906b2f614355 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 12:50:53 +0900 Subject: [PATCH 1235/1621] Rename `Mod.Ranked` back to avoid breaking ruleset API --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 79d16013e3..40c3761768 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Mods public virtual bool UserPlayable => true; [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to true.")] // Can be removed 20211009 - public virtual bool IsRanked => false; + public virtual bool Ranked => false; /// /// Whether this mod requires configuration to apply changes to the game. From 30467191b2137534aa3aaacb4973938bf20104be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 14:21:48 +0900 Subject: [PATCH 1236/1621] Remove local handling of `NoFail` addition --- .../Gameplay/TestScenePlayerScoreSubmission.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 27f9fc85cf..d80fbfe309 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.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.Linq; using NUnit.Framework; using osu.Framework.Screens; @@ -9,7 +8,6 @@ using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Ranking; @@ -17,22 +15,15 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestScenePlayerScoreSubmission : OsuPlayerTestScene { - protected override bool AllowFail => false; + protected override bool AllowFail => allowFail; + + private bool allowFail; private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; protected override bool HasCustomSteps => true; - private bool allowFail; - - protected override TestPlayer CreatePlayer(Ruleset ruleset) - { - SelectedMods.Value = !allowFail - ? new[] { ruleset.GetAllMods().OfType().First() } - : Array.Empty(); - - return new TestPlayer(false); - } + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); [Test] public void TestNoSubmissionOnResultsWithNoToken() From f689e788c9d363136dfc0900acd179a801754b6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 14:24:27 +0900 Subject: [PATCH 1237/1621] Avoid using game-wide beatmap during score submission --- osu.Game/Screens/Play/SoloPlayer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 0099b4c87c..ef1087dd62 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -37,9 +37,11 @@ namespace osu.Game.Screens.Play protected override APIRequest CreateSubmissionRequest(Score score, long token) { - Debug.Assert(Beatmap.Value.BeatmapInfo.OnlineBeatmapID != null); + var beatmap = score.ScoreInfo.Beatmap; - int beatmapId = Beatmap.Value.BeatmapInfo.OnlineBeatmapID.Value; + Debug.Assert(beatmap.OnlineBeatmapID != null); + + int beatmapId = beatmap.OnlineBeatmapID.Value; return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); } From f2d9d78455d2de5495c522cde54b6209dcbbd446 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 14:43:48 +0900 Subject: [PATCH 1238/1621] Fix some incorrectly invoked `async` calls --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 4 ++-- .../Multiplayer/TestSceneMultiplayerReadyButton.cs | 5 +++-- .../Multiplayer/TestSceneMultiplayerSpectateButton.cs | 9 +++++---- osu.Game/OsuGame.cs | 4 ++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 47cd39dc5a..58d67c11d9 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -116,7 +116,7 @@ namespace osu.Desktop.Updater if (scheduleRecheck) { // check again in 30 minutes. - Scheduler.AddDelayed(async () => await checkForUpdateAsync().ConfigureAwait(false), 60000 * 30); + Scheduler.AddDelayed(() => Task.Run(async () => await checkForUpdateAsync().ConfigureAwait(false)), 60000 * 30); } } @@ -141,7 +141,7 @@ namespace osu.Desktop.Updater Activated = () => { updateManager.PrepareUpdateAsync() - .ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit())); + .ContinueWith(_ => updateManager.Schedule(() => game?.GracefullyExit())); return true; }; } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 4f2ca34fb0..fd9aa922b0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -65,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - OnReadyClick = async () => + OnReadyClick = () => Task.Run(async () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); @@ -77,7 +78,7 @@ namespace osu.Game.Tests.Visual.Multiplayer await Client.ToggleReady(); readyClickOperation.Dispose(); - } + }) }); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 070158f552..a4b0195b00 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -69,19 +70,19 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - OnSpectateClick = async () => + OnSpectateClick = () => Task.Run(async () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); await Client.ToggleSpectate(); readyClickOperation.Dispose(); - } + }) }, readyButton = new MultiplayerReadyButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - OnReadyClick = async () => + OnReadyClick = () => Task.Run(async () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); @@ -93,7 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer await Client.ToggleReady(); readyClickOperation.Dispose(); - } + }) } } }; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 14309e2296..dcd2d68b43 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -491,6 +491,10 @@ namespace osu.Game public override Task Import(params ImportTask[] imports) { // encapsulate task as we don't want to begin the import process until in a ready state. + + // ReSharper disable once AsyncVoidLambda + // TODO: This is bad because `new Task` doesn't have a Func override. + // Only used for android imports and a bit of a mess. Probably needs rethinking overall. var importTask = new Task(async () => await base.Import(imports).ConfigureAwait(false)); waitForReady(() => this, _ => importTask.Start()); From 69b13477304ce77d9003cbceac394cf3f571dd88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 15:29:51 +0900 Subject: [PATCH 1239/1621] Tidy up weird bind logic --- osu.Game/Overlays/Volume/VolumeMeter.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 2251be03c6..6a26de204c 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -187,13 +187,7 @@ namespace osu.Game.Overlays.Volume } }; - Bindable.ValueChanged += volume => - { - this.TransformTo("DisplayVolume", - volume.NewValue, - 400, - Easing.OutQuint); - }; + Bindable.ValueChanged += volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }; bgProgress.Current.Value = 0.75f; } From 13254d51fc93a33dbe67921d4a62b112bd4ae2ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 15:44:35 +0900 Subject: [PATCH 1240/1621] Remove usage of `bool` for initial playback Also refactors the whole method to generally clean things up. One more important fix is setting the frequency on the channel before starting playback, so avoid the frequency potentially being adjusted after the playback is already started. --- osu.Game/Overlays/Volume/VolumeMeter.cs | 63 +++++++++++++------------ 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 6a26de204c..3321dcf329 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -38,7 +38,6 @@ namespace osu.Game.Overlays.Volume private OsuSpriteText text; private BufferedContainer maxGlow; - private bool firstUpdate = true; private Sample sample; private double sampleLastPlaybackTime; @@ -187,16 +186,12 @@ namespace osu.Game.Overlays.Volume } }; - Bindable.ValueChanged += volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }; + Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true); bgProgress.Current.Value = 0.75f; } - protected override void LoadComplete() - { - base.LoadComplete(); - Bindable.TriggerChange(); - } + private int displayVolumeInt; private double displayVolume; @@ -205,9 +200,16 @@ namespace osu.Game.Overlays.Volume get => displayVolume; set { - bool displayVolumeChanged = Math.Round(displayVolume * 100) != Math.Round(value * 100); + if (value == displayVolume) + return; + displayVolume = value; + int intValue = (int)Math.Round(displayVolume * 100); + bool intVolumeChanged = intValue != displayVolumeInt; + + displayVolumeInt = intValue; + if (displayVolume >= 0.995f) { text.Text = "MAX"; @@ -216,37 +218,36 @@ namespace osu.Game.Overlays.Volume else { maxGlow.EffectColour = Color4.Transparent; - text.Text = Math.Round(displayVolume * 100).ToString(CultureInfo.CurrentCulture); + text.Text = displayVolumeInt.ToString(CultureInfo.CurrentCulture); } volumeCircle.Current.Value = displayVolume * 0.75f; volumeCircleGlow.Current.Value = displayVolume * 0.75f; - Schedule(() => - { - const int sfx_debounce_time = 30; - - if (firstUpdate || - !displayVolumeChanged || - Time.Current - sampleLastPlaybackTime <= sfx_debounce_time) - { - firstUpdate = false; - return; - } - - var channel = sample.Play(); - - channel.Frequency.Value = 1 + displayVolume * 0.1f + RNG.NextDouble(0.02f); - if (displayVolume < 0.005f) - channel.Frequency.Value -= 0.5f; - else if (displayVolume > 0.995f) - channel.Frequency.Value -= 0.5f; - - sampleLastPlaybackTime = Time.Current; - }); + if (intVolumeChanged && IsLoaded) + Scheduler.AddOnce(playTickSound); } } + private void playTickSound() + { + const int tick_debounce_time = 30; + + if (Time.Current - sampleLastPlaybackTime <= tick_debounce_time) + return; + + var channel = sample.GetChannel(); + + channel.Frequency.Value = 1 + displayVolume * 0.1f + RNG.NextDouble(0.02f); + + if (displayVolumeInt == 0) + channel.Frequency.Value -= 0.5f; + else if (displayVolumeInt == 100) channel.Frequency.Value -= 0.5f; + + channel.Play(); + sampleLastPlaybackTime = Time.Current; + } + public double Volume { get => Bindable.Value; From bd6664d5414532a384563128581f81a46663d64d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 15:45:50 +0900 Subject: [PATCH 1241/1621] Add note about intentional downward pitch, against expectations --- osu.Game/Overlays/Volume/VolumeMeter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 3321dcf329..7428c6a7f6 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -242,7 +242,9 @@ namespace osu.Game.Overlays.Volume if (displayVolumeInt == 0) channel.Frequency.Value -= 0.5f; - else if (displayVolumeInt == 100) channel.Frequency.Value -= 0.5f; + else if (displayVolumeInt == 100) + // intentionally pitched down, even when hitting max. + channel.Frequency.Value -= 0.5f; channel.Play(); sampleLastPlaybackTime = Time.Current; From 910fe3e9f8bde8135cb7f8caabad32b0511bca48 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 2 Jul 2021 15:39:43 +0900 Subject: [PATCH 1242/1621] Center pitch randomisation around base pitch --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 7 +++---- osu.Game/Overlays/Volume/VolumeMeter.cs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 1433f0c38b..386e081e6f 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -161,10 +161,9 @@ namespace osu.Game.Graphics.UserInterface var channel = sample.Play(); - channel.Frequency.Value = 1 + NormalizedValue * 0.2f + RNG.NextDouble(0.02f); - if (NormalizedValue == 0) - channel.Frequency.Value -= 0.5f; - else if (NormalizedValue == 1) + channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + NormalizedValue * 0.2f; + + if (NormalizedValue == 0 || NormalizedValue == 1) channel.Frequency.Value -= 0.5f; } diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 7428c6a7f6..3300d8ee17 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -238,7 +238,7 @@ namespace osu.Game.Overlays.Volume var channel = sample.GetChannel(); - channel.Frequency.Value = 1 + displayVolume * 0.1f + RNG.NextDouble(0.02f); + channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + displayVolume * 0.1f; if (displayVolumeInt == 0) channel.Frequency.Value -= 0.5f; From 63d2ac66d2e3a20863a2d60761d49f357b257f54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 15:52:20 +0900 Subject: [PATCH 1243/1621] Fix one more instance of incorrect playback/frequency set order --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 386e081e6f..0ae77071fb 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -159,12 +159,14 @@ namespace osu.Game.Graphics.UserInterface lastSampleValue = value; lastSampleTime = Clock.CurrentTime; - var channel = sample.Play(); + var channel = sample.GetChannel(); channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + NormalizedValue * 0.2f; if (NormalizedValue == 0 || NormalizedValue == 1) channel.Frequency.Value -= 0.5f; + + channel.Play(); } private void updateTooltipText(T value) From 35f79669223a8f6564589fa1069b3ee4a5b9ca29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 15:55:20 +0900 Subject: [PATCH 1244/1621] Merge conditionals in line with other case of same logic --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 1 + osu.Game/Overlays/Volume/VolumeMeter.cs | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 0ae77071fb..f85f9327fa 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -163,6 +163,7 @@ namespace osu.Game.Graphics.UserInterface channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + NormalizedValue * 0.2f; + // intentionally pitched down, even when hitting max. if (NormalizedValue == 0 || NormalizedValue == 1) channel.Frequency.Value -= 0.5f; diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 3300d8ee17..532b0f4a81 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -240,10 +240,8 @@ namespace osu.Game.Overlays.Volume channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + displayVolume * 0.1f; - if (displayVolumeInt == 0) - channel.Frequency.Value -= 0.5f; - else if (displayVolumeInt == 100) - // intentionally pitched down, even when hitting max. + // intentionally pitched down, even when hitting max. + if (displayVolumeInt == 0 || displayVolumeInt == 100) channel.Frequency.Value -= 0.5f; channel.Play(); From 7b0f970e7db162bf356b33b2977f22c638be15db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 16:04:51 +0900 Subject: [PATCH 1245/1621] Fix ongoing operation being begun in an async context --- .../TestSceneMultiplayerReadyButton.cs | 20 +++++++----- .../TestSceneMultiplayerSpectateButton.cs | 32 ++++++++++++------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index fd9aa922b0..0e036e8868 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -66,19 +66,23 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - OnReadyClick = () => Task.Run(async () => + OnReadyClick = () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); - if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready) + Task.Run(async () => { - await Client.StartMatch(); - return; - } + if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready) + { + await Client.StartMatch(); + return; + } - await Client.ToggleReady(); - readyClickOperation.Dispose(); - }) + await Client.ToggleReady(); + + readyClickOperation.Dispose(); + }); + } }); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index a4b0195b00..4966dfbe50 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -70,31 +70,39 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - OnSpectateClick = () => Task.Run(async () => + OnSpectateClick = () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); - await Client.ToggleSpectate(); - readyClickOperation.Dispose(); - }) + + Task.Run(async () => + { + await Client.ToggleSpectate(); + readyClickOperation.Dispose(); + }); + } }, readyButton = new MultiplayerReadyButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - OnReadyClick = () => Task.Run(async () => + OnReadyClick = () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); - if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready) + Task.Run(async () => { - await Client.StartMatch(); - return; - } + if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready) + { + await Client.StartMatch(); + return; + } - await Client.ToggleReady(); - readyClickOperation.Dispose(); - }) + await Client.ToggleReady(); + + readyClickOperation.Dispose(); + }); + } } } }; From 6a0c5b54c316899168d39f69db1659dc706a0104 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 2 Jul 2021 15:55:25 +0800 Subject: [PATCH 1246/1621] Fix obsolete message in `Mod.Ranked` --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 40c3761768..6f00bb6c75 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool UserPlayable => true; - [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to true.")] // Can be removed 20211009 + [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to false.")] // Can be removed 20211009 public virtual bool Ranked => false; /// From d67fc87dcbc27fd0be7a3fd61faf174099fd9e68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 17:24:15 +0900 Subject: [PATCH 1247/1621] Add some basic testability of external colour setting --- .../Visual/UserInterface/TestSceneColourPicker.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs index 634e45e7a9..fa9179443d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs @@ -2,6 +2,7 @@ // 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.Testing; @@ -14,6 +15,8 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneColourPicker : OsuTestScene { + private readonly Bindable colour = new Bindable(Colour4.Aquamarine); + [SetUpSteps] public void SetUpSteps() { @@ -42,7 +45,8 @@ namespace osu.Game.Tests.Visual.UserInterface new OsuColourPicker { Anchor = Anchor.Centre, - Origin = Anchor.Centre + Origin = Anchor.Centre, + Current = { BindTarget = colour }, } } }, @@ -59,13 +63,18 @@ namespace osu.Game.Tests.Visual.UserInterface new OsuColourPicker { Anchor = Anchor.Centre, - Origin = Anchor.Centre + Origin = Anchor.Centre, + Current = { BindTarget = colour }, } } } } } }); + + AddStep("set green", () => colour.Value = Colour4.LimeGreen); + AddStep("set white", () => colour.Value = Colour4.White); + AddStep("set red", () => colour.Value = Colour4.Red); } private class ColourProvidingContainer : Container From ecb4982281dd71c48b9e3ee0ffde5a2e5919c41b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 17:51:54 +0900 Subject: [PATCH 1248/1621] Add missing blank lines --- osu.Game/Overlays/BeatmapSet/Info.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 89fcbae93d..a1adadbc47 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -163,6 +163,7 @@ namespace osu.Game.Overlays.BeatmapSet { case MetadataType.Tags: string[] tags = value.Split(" "); + for (int i = 0; i <= tags.Length - 1; i++) { textFlow.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i], null, format); @@ -170,6 +171,7 @@ namespace osu.Game.Overlays.BeatmapSet if (i != tags.Length - 1) textFlow.AddText(" ", format); } + break; case MetadataType.Source: From eacf867073b17d204fc53feaacfb4faaa06e4507 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 17:58:24 +0900 Subject: [PATCH 1249/1621] Move shared types into their own classes --- osu.Game/Overlays/BeatmapSet/Info.cs | 75 -------------- osu.Game/Screens/Select/BeatmapDetails.cs | 108 ------------------- osu.Game/Screens/Select/MetadataSection.cs | 115 +++++++++++++++++++++ osu.Game/Screens/Select/MetadataType.cs | 14 +++ 4 files changed, 129 insertions(+), 183 deletions(-) create mode 100644 osu.Game/Screens/Select/MetadataSection.cs create mode 100644 osu.Game/Screens/Select/MetadataType.cs diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index a1adadbc47..8f5d97a6d3 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -6,14 +6,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; using osu.Game.Screens.Select; -using osuTK; namespace osu.Game.Overlays.BeatmapSet { @@ -138,76 +134,5 @@ namespace osu.Game.Overlays.BeatmapSet successRateBackground.Colour = colourProvider.Background4; background.Colour = colourProvider.Background5; } - - private class MetadataSection : FillFlowContainer - { - private readonly MetadataType type; - private readonly LinkFlowContainer textFlow; - - public string Text - { - set - { - if (string.IsNullOrEmpty(value)) - { - Hide(); - return; - } - - this.FadeIn(transition_duration); - - textFlow.Clear(); - static void format(SpriteText t) => t.Font = t.Font.With(size: 12); - - switch (type) - { - case MetadataType.Tags: - string[] tags = value.Split(" "); - - for (int i = 0; i <= tags.Length - 1; i++) - { - textFlow.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i], null, format); - - if (i != tags.Length - 1) - textFlow.AddText(" ", format); - } - - break; - - case MetadataType.Source: - textFlow.AddLink(value, LinkAction.SearchBeatmapSet, value, null, format); - break; - - default: - textFlow.AddText(value, format); - break; - } - } - } - - public MetadataSection(MetadataType type) - { - this.type = type; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Spacing = new Vector2(5f); - - InternalChildren = new Drawable[] - { - new OsuSpriteText - { - Text = this.type.ToString(), - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), - Margin = new MarginPadding { Top = 15 }, - }, - textFlow = new LinkFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - }; - } - } } } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index d7dc73cb37..d6c108cee0 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -15,7 +15,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests; -using osu.Game.Online.Chat; using osu.Game.Rulesets; using osu.Game.Screens.Select.Details; using osuTK; @@ -129,8 +128,6 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Y, LayoutDuration = transition_duration, LayoutEasing = Easing.OutQuad, - Spacing = new Vector2(spacing * 2), - Margin = new MarginPadding { Top = spacing * 2 }, Children = new[] { description = new MetadataSection(MetadataType.Description), @@ -291,110 +288,5 @@ namespace osu.Game.Screens.Select }; } } - - private class MetadataSection : Container - { - private readonly FillFlowContainer textContainer; - private readonly MetadataType type; - private TextFlowContainer textFlow; - - public MetadataSection(MetadataType type) - { - this.type = type; - - Alpha = 0; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - InternalChild = textContainer = new FillFlowContainer - { - Alpha = 0, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(spacing / 2), - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = new OsuSpriteText - { - Text = this.type.ToString(), - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), - }, - }, - }, - }; - } - - public string Text - { - set - { - if (string.IsNullOrEmpty(value)) - { - this.FadeOut(transition_duration); - return; - } - - this.FadeIn(transition_duration); - - setTextAsync(value); - } - } - - private void setTextAsync(string text) - { - LoadComponentAsync(new LinkFlowContainer(s => s.Font = s.Font.With(size: 14)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Colour = Color4.White.Opacity(0.75f), - }, loaded => - { - textFlow?.Expire(); - - switch (type) - { - case MetadataType.Tags: - string[] tags = text.Split(" "); - - for (int i = 0; i <= tags.Length - 1; i++) - { - loaded.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i]); - - if (i != tags.Length - 1) - loaded.AddText(" "); - } - - break; - - case MetadataType.Source: - loaded.AddLink(text, LinkAction.SearchBeatmapSet, text); - break; - - default: - loaded.AddText(text); - break; - } - - textContainer.Add(textFlow = loaded); - - // fade in if we haven't yet. - textContainer.FadeIn(transition_duration); - }); - } - } - } - - public enum MetadataType - { - Tags, - Source, - Description, - Genre, - Language } } diff --git a/osu.Game/Screens/Select/MetadataSection.cs b/osu.Game/Screens/Select/MetadataSection.cs new file mode 100644 index 0000000000..989223fff2 --- /dev/null +++ b/osu.Game/Screens/Select/MetadataSection.cs @@ -0,0 +1,115 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Select +{ + public class MetadataSection : Container + { + private readonly FillFlowContainer textContainer; + private readonly MetadataType type; + private TextFlowContainer textFlow; + + private const float transition_duration = 250; + + public MetadataSection(MetadataType type) + { + this.type = type; + + Alpha = 0; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = textContainer = new FillFlowContainer + { + Alpha = 0, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + + Margin = new MarginPadding { Top = 15 }, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new OsuSpriteText + { + Text = this.type.ToString(), + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), + }, + }, + }, + }; + } + + public string Text + { + set + { + if (string.IsNullOrEmpty(value)) + { + this.FadeOut(transition_duration); + return; + } + + this.FadeIn(transition_duration); + + setTextAsync(value); + } + } + + private void setTextAsync(string text) + { + LoadComponentAsync(new LinkFlowContainer(s => s.Font = s.Font.With(size: 14)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Colour = Color4.White.Opacity(0.75f), + }, loaded => + { + textFlow?.Expire(); + + switch (type) + { + case MetadataType.Tags: + string[] tags = text.Split(" "); + + for (int i = 0; i <= tags.Length - 1; i++) + { + loaded.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i]); + + if (i != tags.Length - 1) + loaded.AddText(" "); + } + + break; + + case MetadataType.Source: + loaded.AddLink(text, LinkAction.SearchBeatmapSet, text); + break; + + default: + loaded.AddText(text); + break; + } + + textContainer.Add(textFlow = loaded); + + // fade in if we haven't yet. + textContainer.FadeIn(transition_duration); + }); + } + } +} diff --git a/osu.Game/Screens/Select/MetadataType.cs b/osu.Game/Screens/Select/MetadataType.cs new file mode 100644 index 0000000000..b0b8f5677d --- /dev/null +++ b/osu.Game/Screens/Select/MetadataType.cs @@ -0,0 +1,14 @@ +// 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.Screens.Select +{ + public enum MetadataType + { + Tags, + Source, + Description, + Genre, + Language + } +} From 362816492fb6e70d246335ad00ed9f151d92ad9f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 18:09:16 +0900 Subject: [PATCH 1250/1621] Move to more friendly namespace --- .../{Screens/Select => Overlays/BeatmapSet}/MetadataSection.cs | 3 ++- .../{Screens/Select => Overlays/BeatmapSet}/MetadataType.cs | 2 +- osu.Game/Screens/Select/BeatmapDetails.cs | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) rename osu.Game/{Screens/Select => Overlays/BeatmapSet}/MetadataSection.cs (97%) rename osu.Game/{Screens/Select => Overlays/BeatmapSet}/MetadataType.cs (87%) diff --git a/osu.Game/Screens/Select/MetadataSection.cs b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs similarity index 97% rename from osu.Game/Screens/Select/MetadataSection.cs rename to osu.Game/Overlays/BeatmapSet/MetadataSection.cs index 989223fff2..50e95bb04b 100644 --- a/osu.Game/Screens/Select/MetadataSection.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs @@ -8,10 +8,11 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; +using osu.Game.Screens.Select; using osuTK; using osuTK.Graphics; -namespace osu.Game.Screens.Select +namespace osu.Game.Overlays.BeatmapSet { public class MetadataSection : Container { diff --git a/osu.Game/Screens/Select/MetadataType.cs b/osu.Game/Overlays/BeatmapSet/MetadataType.cs similarity index 87% rename from osu.Game/Screens/Select/MetadataType.cs rename to osu.Game/Overlays/BeatmapSet/MetadataType.cs index b0b8f5677d..1ab4c6887e 100644 --- a/osu.Game/Screens/Select/MetadataType.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataType.cs @@ -1,7 +1,7 @@ // 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.Screens.Select +namespace osu.Game.Overlays.BeatmapSet { public enum MetadataType { diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index d6c108cee0..973f54c038 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Overlays.BeatmapSet; using osu.Game.Rulesets; using osu.Game.Screens.Select.Details; using osuTK; From 8847915c6a4322d83562e5cb7f2cf65004f33d01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Jul 2021 18:51:14 +0900 Subject: [PATCH 1251/1621] 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 2e388b8626..591e00fb27 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 c44857a77d..14efcd516e 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 4772d700da..0ab0b26430 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 170513568be1a85e89f40ac622a70bee5c2ee753 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 21 Jun 2021 19:08:43 +0900 Subject: [PATCH 1252/1621] Move caught object stack vertical offset logic --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 4 ++-- osu.Game.Rulesets.Catch/UI/Catcher.cs | 10 ++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 1ad45d2f13..927175a138 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -194,8 +194,8 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("catch more fruits", () => attemptCatch(() => new Fruit(), 9)); checkPlate(10); AddAssert("caught objects are stacked", () => - catcher.CaughtObjects.All(obj => obj.Y <= Catcher.CAUGHT_FRUIT_VERTICAL_OFFSET) && - catcher.CaughtObjects.Any(obj => obj.Y == Catcher.CAUGHT_FRUIT_VERTICAL_OFFSET) && + catcher.CaughtObjects.All(obj => obj.Y <= 0) && + catcher.CaughtObjects.Any(obj => obj.Y == 0) && catcher.CaughtObjects.Any(obj => obj.Y < -25)); } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index dcab9459ee..f2e29b2e10 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -56,11 +56,6 @@ namespace osu.Game.Rulesets.Catch.UI /// 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". - /// - public const float CAUGHT_FRUIT_VERTICAL_OFFSET = -5; - /// /// The amount by which caught fruit should be scaled down to fit on the plate. /// @@ -157,6 +152,8 @@ namespace osu.Game.Rulesets.Catch.UI { Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, + // offset fruit vertically to better place "above" the plate. + Y = -5 }, body = new SkinnableCatcher(), hitExplosionContainer = new HitExplosionContainer @@ -388,9 +385,6 @@ namespace osu.Game.Rulesets.Catch.UI float adjustedRadius = displayRadius * lenience_adjust; float checkDistance = MathF.Pow(adjustedRadius, 2); - // offset fruit vertically to better place "above" the plate. - position.Y += CAUGHT_FRUIT_VERTICAL_OFFSET; - while (caughtObjectContainer.Any(f => Vector2Extensions.DistanceSquared(f.Position, position) < checkDistance)) { position.X += RNG.NextSingle(-adjustedRadius, adjustedRadius); From fbba32647e8ed1f44b68c25d9b2957c13142904f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 2 Jul 2021 22:00:41 +0900 Subject: [PATCH 1253/1621] Decouple direction of catcher from its scale --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 21 ++++++++++--------- .../UI/CatcherTrailDisplay.cs | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index dcab9459ee..b6a9863002 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -84,8 +84,8 @@ namespace osu.Game.Rulesets.Catch.UI public CatcherAnimationState CurrentState { - get => body.AnimationState.Value; - private set => body.AnimationState.Value = value; + get => Body.AnimationState.Value; + private set => Body.AnimationState.Value = value; } /// @@ -108,18 +108,14 @@ 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); - } + public Direction VisualDirection { get; set; } = Direction.Right; /// /// Width of the area that can be used to attempt catches during gameplay. /// private readonly float catchWidth; - private readonly SkinnableCatcher body; + internal readonly SkinnableCatcher Body; private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR; private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR; @@ -158,7 +154,7 @@ namespace osu.Game.Rulesets.Catch.UI Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, }, - body = new SkinnableCatcher(), + Body = new SkinnableCatcher(), hitExplosionContainer = new HitExplosionContainer { Anchor = Anchor.TopCentre, @@ -354,6 +350,11 @@ namespace osu.Game.Rulesets.Catch.UI { base.Update(); + var scaleFromDirection = new Vector2((int)VisualDirection, 1); + Body.Scale = scaleFromDirection; + // TODO: don't flip plate contents on legacy skin. + caughtObjectContainer.Scale = hitExplosionContainer.Scale = scaleFromDirection; + // Correct overshooting. if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) || (hyperDashDirection < 0 && hyperDashTargetPosition > X)) @@ -465,7 +466,7 @@ namespace osu.Game.Rulesets.Catch.UI break; case DroppedObjectAnimation.Explode: - var originalX = droppedObjectTarget.ToSpaceOfOtherDrawable(d.DrawPosition, caughtObjectContainer).X * Scale.X; + float originalX = droppedObjectTarget.ToSpaceOfOtherDrawable(d.DrawPosition, caughtObjectContainer).X * caughtObjectContainer.Scale.X; d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine); d.MoveToX(d.X + originalX * 6, 1000); d.FadeOut(750); diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index 7e4a5b6a86..b59fabcb70 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Catch.UI CatcherTrail sprite = trailPool.Get(); sprite.AnimationState = catcher.CurrentState; - sprite.Scale = catcher.Scale; + sprite.Scale = catcher.Scale * catcher.Body.Scale; sprite.Position = catcher.Position; target.Add(sprite); From 83c80291d49b5b65d056019710e400dc0d1c16c6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 2 Jul 2021 22:18:31 +0900 Subject: [PATCH 1254/1621] Don't flip catcher plate contents in legacy skin --- .../Skinning/CatchSkinConfiguration.cs | 13 +++++++++++++ .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 10 ++++++++++ osu.Game.Rulesets.Catch/UI/Catcher.cs | 13 +++++++++++-- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs new file mode 100644 index 0000000000..ea8d742b1a --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs @@ -0,0 +1,13 @@ +// 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.Skinning +{ + public enum CatchSkinConfiguration + { + /// + /// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed. + /// + FlipCatcherPlate + } +} diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 287ed1b4c7..b6f6e91c29 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -103,6 +103,16 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy result.Value = LegacyColourCompatibility.DisallowZeroAlpha(result.Value); return (IBindable)result; + + case CatchSkinConfiguration config: + switch (config) + { + case CatchSkinConfiguration.FlipCatcherPlate: + // Always return `false` (don't flip catcher plate contents) for a legacy skin. + return (IBindable)new Bindable(); + } + + break; } return base.GetConfig(lookup); diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index b6a9863002..4b532b9c91 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -108,8 +108,16 @@ namespace osu.Game.Rulesets.Catch.UI } } + /// + /// The currently facing direction. + /// public Direction VisualDirection { get; set; } = Direction.Right; + /// + /// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed. + /// + private bool flipCatcherPlate; + /// /// Width of the area that can be used to attempt catches during gameplay. /// @@ -343,6 +351,8 @@ namespace osu.Game.Rulesets.Catch.UI trails.HyperDashTrailsColour = hyperDashColour; trails.EndGlowSpritesColour = hyperDashEndGlowColour; + flipCatcherPlate = skin.GetConfig(CatchSkinConfiguration.FlipCatcherPlate)?.Value ?? true; + runHyperDashStateTransition(HyperDashing); } @@ -352,8 +362,7 @@ namespace osu.Game.Rulesets.Catch.UI var scaleFromDirection = new Vector2((int)VisualDirection, 1); Body.Scale = scaleFromDirection; - // TODO: don't flip plate contents on legacy skin. - caughtObjectContainer.Scale = hitExplosionContainer.Scale = scaleFromDirection; + caughtObjectContainer.Scale = hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One; // Correct overshooting. if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) || From 333caca38667c35b10630ed9c111daf0b992e60b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 2 Jul 2021 23:09:37 +0900 Subject: [PATCH 1255/1621] Add test for catcher plate flipping configuration --- .../TestSceneCatchSkinConfiguration.cs | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs new file mode 100644 index 0000000000..ec186bcfb2 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs @@ -0,0 +1,114 @@ +// 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.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +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.UI; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; +using Direction = osu.Game.Rulesets.Catch.UI.Direction; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class TestSceneCatchSkinConfiguration : OsuTestScene + { + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + private Catcher catcher; + + private readonly Container container; + + public TestSceneCatchSkinConfiguration() + { + Add(droppedObjectContainer = new DroppedObjectContainer()); + Add(container = new Container { RelativeSizeAxes = Axes.Both }); + } + + [TestCase(false)] + [TestCase(true)] + public void TestCatcherPlateFlipping(bool flip) + { + AddStep("setup catcher", () => + { + var skin = new TestSkin { FlipCatcherPlate = flip }; + container.Child = new SkinProvidingContainer(skin) + { + Child = catcher = new Catcher(new Container()) + { + Anchor = Anchor.Centre + } + }; + }); + + Fruit fruit = new Fruit(); + + AddStep("catch fruit", () => catchFruit(fruit, 20)); + + float position = 0; + + AddStep("record fruit position", () => position = getCaughtObjectPosition(fruit)); + + AddStep("face left", () => catcher.VisualDirection = Direction.Left); + + if (flip) + AddAssert("fruit position changed", () => !Precision.AlmostEquals(getCaughtObjectPosition(fruit), position)); + else + AddAssert("fruit position unchanged", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position)); + + AddStep("face right", () => catcher.VisualDirection = Direction.Right); + + AddAssert("fruit position restored", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position)); + } + + private float getCaughtObjectPosition(Fruit fruit) + { + var caughtObject = catcher.ChildrenOfType().Single(c => c.HitObject == fruit); + return caughtObject.Parent.ToSpaceOfOtherDrawable(caughtObject.Position, catcher).X; + } + + private void catchFruit(Fruit fruit, float x) + { + fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + var drawableFruit = new DrawableFruit(fruit) { X = x }; + var judgement = fruit.CreateJudgement(); + catcher.OnNewResult(drawableFruit, new CatchJudgementResult(fruit, judgement) + { + Type = judgement.MaxResult + }); + } + + private class TestSkin : DefaultSkin + { + public bool FlipCatcherPlate { get; set; } + + public TestSkin() + : base(null) + { + } + + public override IBindable GetConfig(TLookup lookup) + { + if (lookup is CatchSkinConfiguration config) + { + if (config == CatchSkinConfiguration.FlipCatcherPlate) + return SkinUtils.As(new Bindable(FlipCatcherPlate)); + } + + return base.GetConfig(lookup); + } + } + } +} From 2b366e04fd1fed1506a8e356de951466e040793a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Jul 2021 21:06:57 +0300 Subject: [PATCH 1256/1621] Revert "Fix `RestoreDefaultValueButton` not behaving correctly on number types" This reverts commit bc0ab7dd4f1313e448c63661f3252beb18595e1c. --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 2f0c43dedb..fe36f6ba6d 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays { public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - private readonly IBindableWithCurrent current = IBindableWithCurrent.Create(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); // this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button. public override bool AcceptsFocus => true; @@ -62,8 +62,7 @@ namespace osu.Game.Overlays Action += () => { - if (!Current.Disabled) - Current.SetDefault(); + if (!current.Disabled) current.SetDefault(); }; } @@ -97,12 +96,12 @@ namespace osu.Game.Overlays private void updateState() { - if (Current == null) + if (current == null) return; - this.FadeTo(Current.IsDefault ? 0f : - hovering && !Current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); - this.FadeColour(Current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); + this.FadeTo(current.IsDefault ? 0f : + hovering && !current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); + this.FadeColour(current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); } } } From 612ed6353c909da3d1099222795d8a3abf49a2d0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Jul 2021 22:30:26 +0300 Subject: [PATCH 1257/1621] Resolve `RestoreDefaultValueButton` issue by internal management --- .../Overlays/RestoreDefaultValueButton.cs | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index fe36f6ba6d..cd4490d452 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -20,15 +20,30 @@ namespace osu.Game.Overlays { public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - private readonly BindableWithCurrent current = new BindableWithCurrent(); - // this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button. public override bool AcceptsFocus => true; + private Bindable current; + public Bindable Current { - get => current.Current; - set => current.Current = value; + get => current; + set + { + if (current != null) + { + current.ValueChanged -= onValueChanged; + current.DefaultChanged -= onDefaultChanged; + current.DisabledChanged -= onDisabledChanged; + } + + current = value; + + current.ValueChanged += onValueChanged; + current.DefaultChanged += onDefaultChanged; + current.DisabledChanged += onDisabledChanged; + UpdateState(); + } } private Color4 buttonColour; @@ -62,21 +77,11 @@ namespace osu.Game.Overlays Action += () => { - if (!current.Disabled) current.SetDefault(); + if (!current.Disabled) + current.SetDefault(); }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - Current.ValueChanged += _ => UpdateState(); - Current.DisabledChanged += _ => UpdateState(); - Current.DefaultChanged += _ => UpdateState(); - - UpdateState(); - } - public LocalisableString TooltipText => "revert to default"; protected override bool OnHover(HoverEvent e) @@ -92,6 +97,10 @@ namespace osu.Game.Overlays UpdateState(); } + private void onValueChanged(ValueChangedEvent _) => UpdateState(); + private void onDefaultChanged(ValueChangedEvent _) => UpdateState(); + private void onDisabledChanged(bool _) => UpdateState(); + public void UpdateState() => Scheduler.AddOnce(updateState); private void updateState() From 83578e7c9d878530bf68a5ae2099916ff619824f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Jul 2021 23:24:51 +0300 Subject: [PATCH 1258/1621] Hold a bound copy reference instead --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index cd4490d452..0790929ab2 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -30,14 +30,8 @@ namespace osu.Game.Overlays get => current; set { - if (current != null) - { - current.ValueChanged -= onValueChanged; - current.DefaultChanged -= onDefaultChanged; - current.DisabledChanged -= onDisabledChanged; - } - - current = value; + current?.UnbindAll(); + current = value.GetBoundCopy(); current.ValueChanged += onValueChanged; current.DefaultChanged += onDefaultChanged; From 1470bb15634c0946a08c91fd484b643a5e542659 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 3 Jul 2021 13:02:39 +0800 Subject: [PATCH 1259/1621] Use `IHidesApproachCircles` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index cc3811c9b8..5ca9a15a0f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModTarget : ModWithVisibilityAdjustment, IApplicableToDrawableRuleset, IApplicableToHealthProcessor, IApplicableToDifficulty, IApplicableFailOverride, - IHasSeed, IMutateApproachCircles + IHasSeed, IHidesApproachCircles { public override string Name => "Target"; public override string Acronym => "TP"; @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Practice keeping up with the beat of the song."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles) }; [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))] public Bindable Seed { get; } = new Bindable From 16d08df5e29492c679f031c3bf298c0ff64d78dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Jul 2021 15:22:03 +0200 Subject: [PATCH 1260/1621] Remove mention of direct from xmldoc --- 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 1ac3a69233..2ab1fa3a1f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -367,7 +367,7 @@ namespace osu.Game public void ShowBeatmap(int beatmapId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId)); /// - /// Show Direct with a query. + /// Shows the beatmap listing overlay, with the given in the search box. /// /// The query to search for. public void SearchBeatmapSet(string query) => waitForReady(() => beatmapListing, _ => beatmapListing.ShowWithSearch(query)); From 7dae93ad669eff61cd5fb51173dd3cfc6ca09405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Jul 2021 15:23:26 +0200 Subject: [PATCH 1261/1621] Remove unused using directives --- osu.Game/Overlays/BeatmapSet/Info.cs | 1 - osu.Game/Overlays/BeatmapSet/MetadataSection.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 8f5d97a6d3..5a752e20fb 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Screens.Select; namespace osu.Game.Overlays.BeatmapSet { diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs index 50e95bb04b..3648c55714 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs @@ -8,7 +8,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; -using osu.Game.Screens.Select; using osuTK; using osuTK.Graphics; From 0be75cc4edb4943b7522857a1f2f6375ec498670 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 3 Jul 2021 22:35:46 +0900 Subject: [PATCH 1262/1621] Fix incorrect `base` call causing import optimisation to not work --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 14bddb6319..0d16294c68 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps protected override bool CanSkipImport(BeatmapSetInfo existing, BeatmapSetInfo import) { - if (!base.CanReuseExisting(existing, import)) + if (!base.CanSkipImport(existing, import)) return false; return existing.Beatmaps.Any(b => b.OnlineBeatmapID != null); From 6fb8ed4d070b98854102dc45aecdf622ef0836d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Jul 2021 15:47:46 +0200 Subject: [PATCH 1263/1621] Trim no longer used constant --- osu.Game/Overlays/BeatmapSet/Info.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 5a752e20fb..f9b8de9dba 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -14,7 +14,6 @@ namespace osu.Game.Overlays.BeatmapSet { public class Info : Container { - private const float transition_duration = 250; private const float metadata_width = 175; private const float spacing = 20; private const float base_height = 220; From 8a23dfa6f58f09ca2a73184262d07eeeabc1fdd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Jul 2021 17:58:12 +0200 Subject: [PATCH 1264/1621] Fix optimised import path buffering events without flush --- osu.Game/Database/ArchiveModelManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index f72a43fa01..87bf54f981 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -353,8 +353,6 @@ namespace osu.Game.Database { cancellationToken.ThrowIfCancellationRequested(); - delayEvents(); - bool checkedExisting = false; TModel existing = null; @@ -394,6 +392,8 @@ namespace osu.Game.Database } } + delayEvents(); + try { LogForModel(item, @"Beginning import..."); From 12371f74245632e061c5ef7bb8ab0693d98d2668 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 3 Jul 2021 18:12:22 +0800 Subject: [PATCH 1265/1621] Fix playlist item displays as empty string if no unicode title is present --- osu.Game/Beatmaps/BeatmapMetadata.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index bfc0236db3..713f80d1fe 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -94,7 +94,10 @@ namespace osu.Game.Beatmaps public RomanisableString ToRomanisableString() { string author = Author == null ? string.Empty : $"({Author})"; - return new RomanisableString($"{ArtistUnicode} - {TitleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim()); + var artistUnicode = string.IsNullOrEmpty(ArtistUnicode) ? Artist : ArtistUnicode; + var titleUnicode = string.IsNullOrEmpty(TitleUnicode) ? Title : TitleUnicode; + + return new RomanisableString($"{artistUnicode} - {titleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim()); } [JsonIgnore] From 623ba1591930383b2518ac1b9ad2b2d71f74b873 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 4 Jul 2021 10:23:49 +0900 Subject: [PATCH 1266/1621] Relax caught object stacking test The stacking code currently uses an unseeded RNG and there is a non-zero chance the stack will be very flat (small Y position difference). Technically, `RNG.NextSingle(0, 5)` can return `0`, but extremely unlikely that the all RNG calls return 0. --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 927175a138..8359657f84 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Catch.Tests AddAssert("caught objects are stacked", () => catcher.CaughtObjects.All(obj => obj.Y <= 0) && catcher.CaughtObjects.Any(obj => obj.Y == 0) && - catcher.CaughtObjects.Any(obj => obj.Y < -25)); + catcher.CaughtObjects.Any(obj => obj.Y < 0)); } [Test] From d1862d8cff46945bfb7d90bfc984e1fb176f66af Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 4 Jul 2021 10:01:56 +0800 Subject: [PATCH 1267/1621] Rename `map` to `mapRange` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 5ca9a15a0f..27c352070a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -291,7 +291,7 @@ namespace osu.Game.Rulesets.Osu.Mods var distance = maxComboIndex == 0 ? (float)obj.Radius - : map(obj.ComboIndex, 0, maxComboIndex, (float)obj.Radius, max_base_distance); + : mapRange(obj.ComboIndex, 0, maxComboIndex, (float)obj.Radius, max_base_distance); if (obj.NewCombo) distance *= 1.5f; if (obj.Kiai) distance *= 1.2f; distance = Math.Min(distance_cap, distance); @@ -515,7 +515,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// Beginning of the new range. /// End of the new range. /// The re-mapped number. - private static float map(float value, float fromLow, float fromHigh, float toLow, float toHigh) + private static float mapRange(float value, float fromLow, float fromHigh, float toLow, float toHigh) { return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow; } From ec71deec5149b1c1c2e4b0ffd760772a542fa8ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 12:39:50 +0900 Subject: [PATCH 1268/1621] Remove some mentions of "lazer" I am aware there are more throughout the codebase but intentionally left the remaining mentioned for one reason or another. The intention here is to mainly change user-facing versioning to change the positioning of the "lazer" term (to be where we would expect "cuttingedge" or "beta" to be). --- osu.Desktop/osu.Desktop.csproj | 4 ++-- osu.Desktop/osu.nuspec | 3 +-- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs | 2 +- .../Visual/SongSelect/TestSceneBeatmapDetails.cs | 8 ++++---- osu.Game/OsuGameBase.cs | 4 ++-- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Screens/Menu/Disclaimer.cs | 2 +- osu.Game/Skinning/LegacyColourCompatibility.cs | 2 +- osu.Game/Skinning/SkinInfo.cs | 2 +- osu.Game/Updater/UpdateManager.cs | 2 +- 10 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index ad5c323e9b..53a4e5edf5 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -5,8 +5,8 @@ true A free-to-win rhythm game. Rhythm is just a *click* away! osu! - osu!lazer - osu!lazer + osu! + osu! lazer.ico app.manifest 0.0.0 diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec index fa182f8e70..1757fd7c73 100644 --- a/osu.Desktop/osu.nuspec +++ b/osu.Desktop/osu.nuspec @@ -3,7 +3,7 @@ osulazer 0.0.0 - osu!lazer + osu! ppy Pty Ltd Dean Herbert https://osu.ppy.sh/ @@ -20,4 +20,3 @@ - diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 317649785e..b2311dcb91 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy /// /// 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. + /// This offset is negated to bring all constants into window-space. /// Note: SPINNER_Y_CENTRE + SPINNER_TOP_OFFSET - Position.Y = 240 (=480/2, or half the window-space in osu!stable) /// protected const float SPINNER_TOP_OFFSET = 45f - 16f; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs index 06572f66bf..b4544fbc85 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.SongSelect Version = "All Metrics", Metadata = new BeatmapMetadata { - Source = "osu!lazer", + Source = "osu!", Tags = "this beatmap has all the metrics", }, BaseDifficulty = new BeatmapDifficulty @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.SongSelect Version = "Only Ratings", Metadata = new BeatmapMetadata { - Source = "osu!lazer", + Source = "osu!", Tags = "this beatmap has ratings metrics but not retries or fails", }, BaseDifficulty = new BeatmapDifficulty @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.SongSelect Version = "Only Retries and Fails", Metadata = new BeatmapMetadata { - Source = "osu!lazer", + Source = "osu!", Tags = "this beatmap has retries and fails but no ratings", }, BaseDifficulty = new BeatmapDifficulty @@ -149,7 +149,7 @@ namespace osu.Game.Tests.Visual.SongSelect Version = "No Metrics", Metadata = new BeatmapMetadata { - Source = "osu!lazer", + Source = "osu!", Tags = "this beatmap has no metrics", }, BaseDifficulty = new BeatmapDifficulty diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7954eafdca..5878727ad8 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -80,7 +80,7 @@ namespace osu.Game return @"local " + (DebugUtils.IsDebugBuild ? @"debug" : @"release"); var version = AssemblyVersion; - return $@"{version.Major}.{version.Minor}.{version.Build}"; + return $@"{version.Major}.{version.Minor}.{version.Build}-lazer"; } } @@ -162,7 +162,7 @@ namespace osu.Game public OsuGameBase() { UseDevelopmentServer = DebugUtils.IsDebugBuild; - Name = @"osu!lazer"; + Name = @"osu!"; } [BackgroundDependencyLoader] diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index d5bea0affc..ebbdc8a109 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -208,7 +208,7 @@ namespace osu.Game.Scoring } else { - // This score is guaranteed to be an osu!lazer score. + // This is guaranteed to be a non-legacy score. // The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values. beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetOrDefault(r)).Sum(); } diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index b0a2fad813..a71b53157a 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -230,7 +230,7 @@ namespace osu.Game.Screens.Menu "New features are coming online every update. Make sure to stay up-to-date!", "If you find the UI too large or small, try adjusting UI scale in settings!", "Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!", - "For now, what used to be \"osu!direct\" is available to all users on lazer. You can access it anywhere using Ctrl-D!", + "What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-D!", "Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!", "Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!", "Try scrolling down in the mod select panel to find a bunch of new fun mods!", diff --git a/osu.Game/Skinning/LegacyColourCompatibility.cs b/osu.Game/Skinning/LegacyColourCompatibility.cs index b842b50426..38e43432ce 100644 --- a/osu.Game/Skinning/LegacyColourCompatibility.cs +++ b/osu.Game/Skinning/LegacyColourCompatibility.cs @@ -7,7 +7,7 @@ using osuTK.Graphics; namespace osu.Game.Skinning { /// - /// Compatibility methods to convert osu!stable colours to osu!lazer-compatible ones. Should be used for legacy skins only. + /// Compatibility methods to apply osu!stable quirks to colours. Should be used for legacy skins only. /// public static class LegacyColourCompatibility { diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index e30bc16d8b..851d71f914 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -46,7 +46,7 @@ namespace osu.Game.Skinning public static SkinInfo Default { get; } = new SkinInfo { ID = DEFAULT_SKIN, - Name = "osu!lazer", + Name = "osu! (triangles)", Creator = "team osu!", InstantiationInfo = typeof(DefaultSkin).GetInvariantInstantiationInfo() }; diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 1c72f3ebe2..98ce2cb46c 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -90,7 +90,7 @@ namespace osu.Game.Updater public UpdateCompleteNotification(string version) { this.version = version; - Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; + Text = $"You are now running osu! {version}.\nClick to see what's new!"; } [BackgroundDependencyLoader] From 149a200f34fd4b6b6108cfd646dc7344f06ab7a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 14:56:15 +0900 Subject: [PATCH 1269/1621] Fix volume metre not correctly showing volume when initially zero Closes https://github.com/ppy/osu/issues/13761. --- osu.Game/Overlays/Volume/VolumeMeter.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 532b0f4a81..1832841476 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -191,7 +191,7 @@ namespace osu.Game.Overlays.Volume bgProgress.Current.Value = 0.75f; } - private int displayVolumeInt; + private int? displayVolumeInt; private double displayVolume; @@ -200,9 +200,6 @@ namespace osu.Game.Overlays.Volume get => displayVolume; set { - if (value == displayVolume) - return; - displayVolume = value; int intValue = (int)Math.Round(displayVolume * 100); @@ -218,7 +215,7 @@ namespace osu.Game.Overlays.Volume else { maxGlow.EffectColour = Color4.Transparent; - text.Text = displayVolumeInt.ToString(CultureInfo.CurrentCulture); + text.Text = intValue.ToString(CultureInfo.CurrentCulture); } volumeCircle.Current.Value = displayVolume * 0.75f; From ef825283090514dbf69b1bee27c6cdeff1b64f00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 15:14:25 +0900 Subject: [PATCH 1270/1621] Don't attempt to submit score when nothing has been hit --- osu.Game/Screens/Play/SubmittingPlayer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 7c5a06707d..9607cc245a 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -10,6 +10,7 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; namespace osu.Game.Screens.Play @@ -144,6 +145,10 @@ namespace osu.Game.Screens.Play if (scoreSubmissionSource != null) return scoreSubmissionSource.Task; + // if the user never hit anything, this score should not be counted in any way. + if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit())) + return Task.CompletedTask; + scoreSubmissionSource = new TaskCompletionSource(); var request = CreateSubmissionRequest(score, token.Value); From 567e9f33a9b74e577761b1eb62564d35be78e8d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 16:24:39 +0900 Subject: [PATCH 1271/1621] Fix thread safety of realm `Refresh` operation Due to the lack of locking, there was a chance the the update thread `context` was retrieved just before the `flushContexts` call, followed by `.Refresh()` being run while the blocking behaviour was invoked. This can be seen in test failures such as https://ci.appveyor.com/project/peppy/osu/builds/39859786/tests. As an aside, I tried multiple different methods to avoid `lock()` on the update thread but they felt flaky. The overhead of lock when there's no contention is reportedly around 30-50ns, so likely not of concern. We can address it at a later point if it becomes one. --- osu.Game/Database/RealmContextFactory.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index fb5e2faff8..3354b97849 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -38,6 +38,8 @@ namespace osu.Game.Database private static readonly GlobalStatistic pending_writes = GlobalStatistics.Get("Realm", "Pending writes"); private static readonly GlobalStatistic active_usages = GlobalStatistics.Get("Realm", "Active usages"); + private readonly object updateContextLock = new object(); + private Realm context; public Realm Context @@ -107,8 +109,11 @@ namespace osu.Game.Database { base.Update(); - if (context?.Refresh() == true) - refreshes.Value++; + lock (updateContextLock) + { + if (context?.Refresh() == true) + refreshes.Value++; + } } private Realm createContext() @@ -156,7 +161,9 @@ namespace osu.Game.Database Logger.Log(@"Flushing realm contexts...", LoggingTarget.Database); var previousContext = context; - context = null; + + lock (updateContextLock) + context = null; // wait for all threaded usages to finish while (active_usages.Value > 0) From 7a710ceffe9cb8eb46b374b6ee07d23e977912ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 16:41:09 +0900 Subject: [PATCH 1272/1621] Check count as well (statistics can be populated with zero counts) --- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 9607cc245a..76e9f28dae 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -146,7 +146,7 @@ namespace osu.Game.Screens.Play return scoreSubmissionSource.Task; // if the user never hit anything, this score should not be counted in any way. - if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit())) + if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit() && s.Value > 0)) return Task.CompletedTask; scoreSubmissionSource = new TaskCompletionSource(); From 2a74b1c5399b7f149abe68d15e9b3337217e1906 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 17:26:46 +0900 Subject: [PATCH 1273/1621] Add test coverage of new scenarios --- .../TestScenePlayerScoreSubmission.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index d80fbfe309..0c1979ad21 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -9,6 +9,8 @@ using osu.Game.Online.Rooms; using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; namespace osu.Game.Tests.Visual.Gameplay @@ -52,6 +54,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for token request", () => Player.TokenCreationRequested); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + + addFakeHit(); + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); @@ -71,6 +76,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("ensure no submission", () => Player.SubmittedScore == null); } + [Test] + public void TestNoSubmissionOnEmptyFail() + { + prepareTokenResponse(true); + + CreateTest(() => allowFail = true); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddUntilStep("wait for fail", () => Player.HasFailed); + AddStep("exit", () => Player.Exit()); + + AddAssert("ensure no submission", () => Player.SubmittedScore == null); + } + [Test] public void TestSubmissionOnFail() { @@ -79,12 +99,28 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(() => allowFail = true); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + addFakeHit(); + AddUntilStep("wait for fail", () => Player.HasFailed); AddStep("exit", () => Player.Exit()); AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); } + [Test] + public void TestNoSubmissionOnEmptyExit() + { + prepareTokenResponse(true); + + CreateTest(() => allowFail = false); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddStep("exit", () => Player.Exit()); + AddAssert("ensure no submission", () => Player.SubmittedScore == null); + } + [Test] public void TestSubmissionOnExit() { @@ -93,10 +129,27 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(() => allowFail = false); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + addFakeHit(); + AddStep("exit", () => Player.Exit()); AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); } + private void addFakeHit() + { + AddUntilStep("wait for first result", () => Player.Results.Count > 0); + + AddStep("force successfuly hit", () => + { + Player.ScoreProcessor.RevertResult(Player.Results.First()); + Player.ScoreProcessor.ApplyResult(new OsuJudgementResult(Beatmap.Value.Beatmap.HitObjects.First(), new OsuJudgement()) + { + Type = HitResult.Great, + }); + }); + } + private void prepareTokenResponse(bool validToken) { AddStep("Prepare test API", () => From 3ec7dc3bb94972fe16a7359db595faf8136bf5fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Jul 2021 17:59:39 +0900 Subject: [PATCH 1274/1621] Update tests in line with thread safety check --- .../Database/TestRealmKeyBindingStore.cs | 54 +++++++++++-------- osu.Game/Database/RealmContextFactory.cs | 21 +++++--- 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index cac331451b..642ecf00b8 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -38,19 +38,28 @@ namespace osu.Game.Tests.Database [Test] public void TestDefaultsPopulationAndQuery() { - Assert.That(query().Count, Is.EqualTo(0)); + Assert.That(queryCount(), Is.EqualTo(0)); KeyBindingContainer testContainer = new TestKeyBindingContainer(); keyBindingStore.Register(testContainer); - Assert.That(query().Count, Is.EqualTo(3)); + Assert.That(queryCount(), Is.EqualTo(3)); - 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)); + Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(1)); + Assert.That(queryCount(GlobalAction.Select), Is.EqualTo(2)); } - private IQueryable query() => realmContextFactory.Context.All(); + private int queryCount(GlobalAction? match = null) + { + using (var usage = realmContextFactory.GetForRead()) + { + var results = usage.Realm.All(); + if (match.HasValue) + results = results.Where(k => k.ActionInt == (int)match.Value); + return results.Count(); + } + } [Test] public void TestUpdateViaQueriedReference() @@ -59,25 +68,28 @@ namespace osu.Game.Tests.Database keyBindingStore.Register(testContainer); - var backBinding = query().Single(k => k.ActionInt == (int)GlobalAction.Back); - - Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape })); - - var tsr = ThreadSafeReference.Create(backBinding); - - using (var usage = realmContextFactory.GetForWrite()) + using (var primaryUsage = realmContextFactory.GetForRead()) { - var binding = usage.Realm.ResolveReference(tsr); - binding.KeyCombination = new KeyCombination(InputKey.BackSpace); + var backBinding = primaryUsage.Realm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); - usage.Commit(); + Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape })); + + var tsr = ThreadSafeReference.Create(backBinding); + + 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 })); + + // check still correct after re-query. + backBinding = primaryUsage.Realm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); + Assert.That(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.ActionInt == (int)GlobalAction.Back); - Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); } [TearDown] diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 3354b97849..f706c37419 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -4,6 +4,7 @@ using System; using System.Threading; using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Platform; @@ -46,15 +47,21 @@ namespace osu.Game.Database { get { - if (context == null) + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException($"Use {nameof(GetForRead)} when performing realm operations from a non-update thread"); + + lock (updateContextLock) { - context = createContext(); - Logger.Log($"Opened realm \"{context.Config.DatabasePath}\" at version {context.Config.SchemaVersion}"); + 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; } - - // creating a context will ensure our schema is up-to-date and migrated. - - return context; } } From b89521314f7b8f72691a865f79b3574f24ba575c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Jul 2021 12:07:31 +0200 Subject: [PATCH 1275/1621] Mention alternatives to `Context` when not on update thread in xmldoc --- osu.Game/Database/IRealmFactory.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/IRealmFactory.cs b/osu.Game/Database/IRealmFactory.cs index c79442134c..0e93e5bf4f 100644 --- a/osu.Game/Database/IRealmFactory.cs +++ b/osu.Game/Database/IRealmFactory.cs @@ -9,6 +9,7 @@ namespace osu.Game.Database { /// /// The main realm context, bound to the update thread. + /// If querying from a non-update thread is needed, use or to receive a context instead. /// Realm Context { get; } From 3291f15dccee50bcaac9f042e7b830fff528236e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Jul 2021 12:08:15 +0200 Subject: [PATCH 1276/1621] Mention `GetForWrite()` as another alternative to `Context` accesses --- osu.Game/Database/RealmContextFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index f706c37419..b0241fb93c 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -48,7 +48,7 @@ namespace osu.Game.Database get { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException($"Use {nameof(GetForRead)} when performing realm operations from a non-update thread"); + throw new InvalidOperationException($"Use {nameof(GetForRead)} or {nameof(GetForWrite)} when performing realm operations from a non-update thread"); lock (updateContextLock) { From 19f02dc3d90f6e16ed23f28e696c1f2bcb2cdf90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Jul 2021 14:34:52 +0200 Subject: [PATCH 1277/1621] Ensure tests with no token have at least one hit Because submission can be prevented by both not having been issued a correct submission token, and by not actually registering any hits in gameplay, ensure that tests that don't receive a token register at least one hit, to avoid potentially having test cases that test the "no token" flow pass erroneously because they never had any hits in the first place. --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 0c1979ad21..d9c0544d3c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -37,6 +37,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for token request", () => Player.TokenCreationRequested); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + + addFakeHit(); + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); @@ -72,6 +75,10 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + + addFakeHit(); + AddStep("exit", () => Player.Exit()); AddAssert("ensure no submission", () => Player.SubmittedScore == null); } From d1553f086413f786e4450aa3ac0333efd2e3359d Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 14:47:07 +0200 Subject: [PATCH 1278/1621] Implement ability to switch between volume meters --- .../Input/Bindings/GlobalActionContainer.cs | 9 +++ .../Overlays/Volume/VolumeControlReceptor.cs | 2 + osu.Game/Overlays/Volume/VolumeMeter.cs | 26 ++++++- osu.Game/Overlays/VolumeOverlay.cs | 69 +++++++++++++++---- 4 files changed, 91 insertions(+), 15 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index c8227c0887..2482be90ee 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -103,6 +103,9 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Alt, InputKey.Up }, GlobalAction.IncreaseVolume), new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume), + new KeyBinding(new[] { InputKey.Alt, InputKey.Left }, GlobalAction.PreviousVolumeMeter), + new KeyBinding(new[] { InputKey.Alt, InputKey.Right }, GlobalAction.NextVolumeMeter), + new KeyBinding(new[] { InputKey.Control, InputKey.F4 }, GlobalAction.ToggleMute), new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev), @@ -263,5 +266,11 @@ namespace osu.Game.Input.Bindings [Description("Toggle skin editor")] ToggleSkinEditor, + + [Description("Previous volume meter")] + PreviousVolumeMeter, + + [Description("Next volume meter")] + NextVolumeMeter, } } diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index ae9c2eb394..b24214ff3d 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -30,6 +30,8 @@ namespace osu.Game.Overlays.Volume return true; case GlobalAction.ToggleMute: + case GlobalAction.NextVolumeMeter: + case GlobalAction.PreviousVolumeMeter: ActionRequested?.Invoke(action); return true; } diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 532b0f4a81..df8f1b7012 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Transforms; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -27,6 +28,11 @@ namespace osu.Game.Overlays.Volume { public class VolumeMeter : Container, IKeyBindingHandler { + [Resolved(canBeNull: true)] + private Bindable focusedMeter { get; set; } + + private bool isFocused => focusedMeter == null || focusedMeter.Value == this; + private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -41,6 +47,8 @@ namespace osu.Game.Overlays.Volume private Sample sample; private double sampleLastPlaybackTime; + public Action RequestFocus; + public VolumeMeter(string name, float circleSize, Color4 meterColour) { this.circleSize = circleSize; @@ -310,20 +318,32 @@ namespace osu.Game.Overlays.Volume private const float transition_length = 500; + public void Focus() + { + if (focusedMeter != null) + focusedMeter.Value = this; + + this.ScaleTo(1.04f, transition_length, Easing.OutExpo); + } + + public void Unfocus() + { + this.ScaleTo(1f, transition_length, Easing.OutExpo); + } + protected override bool OnHover(HoverEvent e) { - this.ScaleTo(1.04f, transition_length, Easing.OutExpo); + Focus(); return false; } protected override void OnHoverLost(HoverLostEvent e) { - this.ScaleTo(1f, transition_length, Easing.OutExpo); } public bool OnPressed(GlobalAction action) { - if (!IsHovered) + if (!IsHovered || !isFocused) return false; switch (action) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index eb639431ae..750d54e091 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -19,6 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { + [Cached] public class VolumeOverlay : VisibilityContainer { private const float offset = 10; @@ -32,6 +33,8 @@ namespace osu.Game.Overlays public Bindable IsMuted { get; } = new Bindable(); + private FillFlowContainer volumeMeters; + [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) { @@ -53,7 +56,7 @@ namespace osu.Game.Overlays Margin = new MarginPadding(10), Current = { BindTarget = IsMuted } }, - new FillFlowContainer + volumeMeters = new FillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, @@ -61,7 +64,7 @@ namespace osu.Game.Overlays Origin = Anchor.CentreLeft, Spacing = new Vector2(0, offset), Margin = new MarginPadding { Left = offset }, - Children = new Drawable[] + Children = new VolumeMeter[] { volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker), volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker), @@ -81,8 +84,13 @@ namespace osu.Game.Overlays else audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); }); + + focusedMeter.BindValueChanged(meter => meter.OldValue?.Unfocus()); } + [Cached] + private Bindable focusedMeter = new Bindable(); + protected override void LoadComplete() { base.LoadComplete(); @@ -93,6 +101,23 @@ namespace osu.Game.Overlays muteButton.Current.ValueChanged += _ => Show(); } + public bool HandleAction(GlobalAction action) + { + if (!IsLoaded) return false; + + switch (action) + { + case GlobalAction.DecreaseVolume: + case GlobalAction.IncreaseVolume: + return Adjust(action); + case GlobalAction.NextVolumeMeter: + return true; + + } + + return true; + } + public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false) { if (!IsLoaded) return false; @@ -102,25 +127,32 @@ namespace osu.Game.Overlays case GlobalAction.DecreaseVolume: if (State.Value == Visibility.Hidden) Show(); - else if (volumeMeterMusic.IsHovered) - volumeMeterMusic.Decrease(amount, isPrecise); - else if (volumeMeterEffect.IsHovered) - volumeMeterEffect.Decrease(amount, isPrecise); else - volumeMeterMaster.Decrease(amount, isPrecise); + focusedMeter.Value.Decrease(amount, isPrecise); return true; case GlobalAction.IncreaseVolume: if (State.Value == Visibility.Hidden) Show(); - else if (volumeMeterMusic.IsHovered) - volumeMeterMusic.Increase(amount, isPrecise); - else if (volumeMeterEffect.IsHovered) - volumeMeterEffect.Increase(amount, isPrecise); else - volumeMeterMaster.Increase(amount, isPrecise); + focusedMeter.Value.Increase(amount, isPrecise); return true; + case GlobalAction.NextVolumeMeter: + if (State.Value == Visibility.Hidden) + Show(); + else + focusShift(1); + return true; + + case GlobalAction.PreviousVolumeMeter: + if (State.Value == Visibility.Hidden) + Show(); + else + focusShift(-1); + return true; + + case GlobalAction.ToggleMute: Show(); muteButton.Current.Value = !muteButton.Current.Value; @@ -130,10 +162,23 @@ namespace osu.Game.Overlays return false; } + private void focusShift(int direction = 1) + { + Show(); + var newIndex = volumeMeters.IndexOf(focusedMeter.Value) + direction; + if (newIndex < 0) + newIndex += volumeMeters.Count; + + volumeMeters.Children[newIndex % volumeMeters.Count].Focus(); + } + private ScheduledDelegate popOutDelegate; public override void Show() { + if (State.Value == Visibility.Hidden) + volumeMeterMaster.Focus(); + if (State.Value == Visibility.Visible) schedulePopOut(); From e151c7ffd017fe07aa7a6555dad88425b1fb1c1c Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 15:31:43 +0200 Subject: [PATCH 1279/1621] Let VolumeMeter request focus instead of taking it --- osu.Game/Overlays/Volume/VolumeMeter.cs | 13 +++------- osu.Game/Overlays/VolumeOverlay.cs | 33 +++++++++++++++++-------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index df8f1b7012..530a10ea7b 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Transforms; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -28,10 +27,7 @@ namespace osu.Game.Overlays.Volume { public class VolumeMeter : Container, IKeyBindingHandler { - [Resolved(canBeNull: true)] - private Bindable focusedMeter { get; set; } - - private bool isFocused => focusedMeter == null || focusedMeter.Value == this; + private bool isFocused = true; private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -320,20 +316,19 @@ namespace osu.Game.Overlays.Volume public void Focus() { - if (focusedMeter != null) - focusedMeter.Value = this; - + isFocused = true; this.ScaleTo(1.04f, transition_length, Easing.OutExpo); } public void Unfocus() { + isFocused = false; this.ScaleTo(1f, transition_length, Easing.OutExpo); } protected override bool OnHover(HoverEvent e) { - Focus(); + RequestFocus?.Invoke(this); return false; } diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 750d54e091..c9321f5d60 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -19,7 +19,6 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - [Cached] public class VolumeOverlay : VisibilityContainer { private const float offset = 10; @@ -64,11 +63,20 @@ namespace osu.Game.Overlays Origin = Anchor.CentreLeft, Spacing = new Vector2(0, offset), Margin = new MarginPadding { Left = offset }, - Children = new VolumeMeter[] + Children = new [] { - volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker), - volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker), - volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker), + volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker) + { + RequestFocus = v => focusedMeter.Value = v + }, + volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker) + { + RequestFocus = v => focusedMeter.Value = v + }, + volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker) + { + RequestFocus = v => focusedMeter.Value = v + }, } } }); @@ -85,11 +93,14 @@ namespace osu.Game.Overlays audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); }); - focusedMeter.BindValueChanged(meter => meter.OldValue?.Unfocus()); + focusedMeter.BindValueChanged(meter => + { + meter.OldValue?.Unfocus(); + meter.NewValue?.Focus(); + }); } - [Cached] - private Bindable focusedMeter = new Bindable(); + private readonly Bindable focusedMeter = new Bindable(); protected override void LoadComplete() { @@ -165,19 +176,21 @@ namespace osu.Game.Overlays private void focusShift(int direction = 1) { Show(); + var newIndex = volumeMeters.IndexOf(focusedMeter.Value) + direction; if (newIndex < 0) newIndex += volumeMeters.Count; - volumeMeters.Children[newIndex % volumeMeters.Count].Focus(); + focusedMeter.Value = volumeMeters.Children[newIndex % volumeMeters.Count]; } private ScheduledDelegate popOutDelegate; public override void Show() { + // Focus on the master meter as a default if previously hidden if (State.Value == Visibility.Hidden) - volumeMeterMaster.Focus(); + focusedMeter.Value = volumeMeterMaster; if (State.Value == Visibility.Visible) schedulePopOut(); From d0707079b1011d2107a0fd00d3117bd83c78c6d4 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 15:35:51 +0200 Subject: [PATCH 1280/1621] Remove unused method --- osu.Game/Overlays/VolumeOverlay.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index c9321f5d60..cad2c81bb5 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -112,23 +112,6 @@ namespace osu.Game.Overlays muteButton.Current.ValueChanged += _ => Show(); } - public bool HandleAction(GlobalAction action) - { - if (!IsLoaded) return false; - - switch (action) - { - case GlobalAction.DecreaseVolume: - case GlobalAction.IncreaseVolume: - return Adjust(action); - case GlobalAction.NextVolumeMeter: - return true; - - } - - return true; - } - public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false) { if (!IsLoaded) return false; From 50c9e17e52ff6963fda8574d520e8122fdf1b755 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 15:42:26 +0200 Subject: [PATCH 1281/1621] Return focus when using UP/DOWN on unfocused meter --- osu.Game/Overlays/Volume/VolumeMeter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 530a10ea7b..1c997a0abc 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -338,9 +338,13 @@ namespace osu.Game.Overlays.Volume public bool OnPressed(GlobalAction action) { - if (!IsHovered || !isFocused) + if (!IsHovered) return false; + // Grab back focus is UP/DOWN input is directed at this meter + if (!isFocused) + RequestFocus?.Invoke(this); + switch (action) { case GlobalAction.SelectPrevious: From 14a861003af7104d4ad06ca19f4ae299678eca79 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 16:06:28 +0200 Subject: [PATCH 1282/1621] Fix code quality errors --- osu.Game/Overlays/VolumeOverlay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index cad2c81bb5..dca39774f2 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -63,7 +63,7 @@ namespace osu.Game.Overlays Origin = Anchor.CentreLeft, Spacing = new Vector2(0, offset), Margin = new MarginPadding { Left = offset }, - Children = new [] + Children = new[] { volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker) { @@ -146,7 +146,6 @@ namespace osu.Game.Overlays focusShift(-1); return true; - case GlobalAction.ToggleMute: Show(); muteButton.Current.Value = !muteButton.Current.Value; From 44d540eb53f6e169de2cede447e8079fac89054a Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 4 Jul 2021 22:09:23 +0800 Subject: [PATCH 1283/1621] Add test --- .../BeatmapMetadataRomanisationTest.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs diff --git a/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs b/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs new file mode 100644 index 0000000000..582864079d --- /dev/null +++ b/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs @@ -0,0 +1,31 @@ +// 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.Beatmaps; + +namespace osu.Game.Tests.Localisation +{ + [TestFixture] + public class BeatmapMetadataRomanisationTest + { + [Test] + public void TestNoUnicode() + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Artist = "Artist", + Title = "Romanised title" + } + } + }; + var romanisableString = beatmap.Metadata.ToRomanisableString(); + + Assert.AreEqual(romanisableString.Romanised, romanisableString.Original); + } + } +} From 356f5dceef8e898dc592b8630b82b1bfda3eecfa Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 4 Jul 2021 22:31:08 +0800 Subject: [PATCH 1284/1621] Add more test case --- .../BeatmapMetadataRomanisationTest.cs | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs b/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs index 582864079d..dab4825919 100644 --- a/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs +++ b/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs @@ -10,20 +10,30 @@ namespace osu.Game.Tests.Localisation public class BeatmapMetadataRomanisationTest { [Test] - public void TestNoUnicode() + public void TestRomanisation() { - var beatmap = new Beatmap + var metadata = new BeatmapMetadata { - BeatmapInfo = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Artist = "Artist", - Title = "Romanised title" - } - } + Artist = "Romanised Artist", + ArtistUnicode = "Unicode Artist", + Title = "Romanised title", + TitleUnicode = "Unicode Title" }; - var romanisableString = beatmap.Metadata.ToRomanisableString(); + var romanisableString = metadata.ToRomanisableString(); + + Assert.AreEqual(metadata.ToString(), romanisableString.Romanised); + Assert.AreEqual($"{metadata.ArtistUnicode} - {metadata.TitleUnicode}", romanisableString.Original); + } + + [Test] + public void TestRomanisationNoUnicode() + { + var metadata = new BeatmapMetadata + { + Artist = "Romanised Artist", + Title = "Romanised title" + }; + var romanisableString = metadata.ToRomanisableString(); Assert.AreEqual(romanisableString.Romanised, romanisableString.Original); } From 69803105efbaee00bd3aa8494cb0d4bf5c74b99f Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 17:19:44 +0200 Subject: [PATCH 1285/1621] Fix volume meter requesting focus for any action --- osu.Game/Overlays/Volume/VolumeMeter.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 1c997a0abc..55f7588741 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -27,8 +27,6 @@ namespace osu.Game.Overlays.Volume { public class VolumeMeter : Container, IKeyBindingHandler { - private bool isFocused = true; - private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -316,13 +314,11 @@ namespace osu.Game.Overlays.Volume public void Focus() { - isFocused = true; this.ScaleTo(1.04f, transition_length, Easing.OutExpo); } public void Unfocus() { - isFocused = false; this.ScaleTo(1f, transition_length, Easing.OutExpo); } @@ -341,17 +337,15 @@ namespace osu.Game.Overlays.Volume if (!IsHovered) return false; - // Grab back focus is UP/DOWN input is directed at this meter - if (!isFocused) - RequestFocus?.Invoke(this); - switch (action) { case GlobalAction.SelectPrevious: + RequestFocus?.Invoke(this); adjust(1, false); return true; case GlobalAction.SelectNext: + RequestFocus?.Invoke(this); adjust(-1, false); return true; } From 32b068fbdc93d3298bf3257c7d6aa9e6b9749119 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 21:50:58 +0200 Subject: [PATCH 1286/1621] Fix typo causing nested windows to be ignored --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 8dcc1ca164..cccd73f19f 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -496,7 +496,7 @@ namespace osu.Game.Rulesets.UI foreach (var n in h.NestedHitObjects) { - if (h.HitWindows.WindowFor(HitResult.Miss) > 0) + if (n.HitWindows.WindowFor(HitResult.Miss) > 0) return n.HitWindows; } } From 6d2ffe3a94ec9edab261682d16887e6dd519c70e Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 22:51:35 +0200 Subject: [PATCH 1287/1621] Add basic tests --- .../NonVisual/FirstAvailableHitWindowTest.cs | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs new file mode 100644 index 0000000000..92766d8334 --- /dev/null +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs @@ -0,0 +1,121 @@ +// 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.CodeAnalysis; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; + +namespace osu.Game.Tests.NonVisual +{ + public class FirstAvailableHitWindowsTest + { + private TestDrawableRuleset testDrawableRuleset = new TestDrawableRuleset(); + + [Test] + public void TestResultIfOnlyParentHitWindowIsEmpty() + { + var testObject = new TestHitObject(hitWindows: HitWindows.Empty); + HitObject nested = new TestHitObject(hitWindows: new HitWindows()); + testObject.AddNested(nested); + testDrawableRuleset.HitObjects = new List { testObject }; + + // If the parent window is empty, but its nested object isn't, return the nested object + Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, nested.HitWindows); + } + + [Test] + public void TestResultIfParentHitWindowsIsNotEmpty() + { + var testObject = new TestHitObject(hitWindows: new HitWindows()); + HitObject nested = new TestHitObject(hitWindows: new HitWindows()); + testObject.AddNested(nested); + testDrawableRuleset.HitObjects = new List { testObject }; + + // If the parent window is not empty, return that immediately + Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, testObject.HitWindows); + } + + [Test] + public void TestResultIfParentAndChildHitWindowsAreEmpty() + { + var firstObject = new TestHitObject(hitWindows: HitWindows.Empty); + HitObject nested = new TestHitObject(hitWindows: HitWindows.Empty); + firstObject.AddNested(nested); + + var secondObject = new TestHitObject(hitWindows: new HitWindows()); + testDrawableRuleset.HitObjects = new List { firstObject, secondObject }; + + // If the parent and child windows are empty, return the next object if window isn't empty + Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, secondObject.HitWindows); + } + + [Test] + public void TestResultIfAllHitWindowsAreEmpty() + { + var firstObject = new TestHitObject(hitWindows: HitWindows.Empty); + HitObject nested = new TestHitObject(hitWindows: HitWindows.Empty); + firstObject.AddNested(nested); + + testDrawableRuleset.HitObjects = new List { firstObject }; + + // If all windows are empty, this should return null + Assert.IsNull(testDrawableRuleset.FirstAvailableHitWindows); + } + + [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] + private class TestDrawableRuleset : DrawableRuleset + { + public List HitObjects; + public override IEnumerable Objects => HitObjects; + + public override event Action NewResult; + public override event Action RevertResult; + + public override Playfield Playfield { get; } + public override Container Overlays { get; } + public override Container FrameStableComponents { get; } + public override IFrameStableClock FrameStableClock { get; } + internal override bool FrameStablePlayback { get; set; } + public override IReadOnlyList Mods { get; } + + public override double GameplayStartTime { get; } + public override GameplayCursorContainer Cursor { get; } + + public TestDrawableRuleset() + : base(new OsuRuleset()) + { + // won't compile without this. + NewResult?.Invoke(null); + RevertResult?.Invoke(null); + } + + public override void SetReplayScore(Score replayScore) => throw new NotImplementedException(); + + public override void SetRecordTarget(Score score) => throw new NotImplementedException(); + + public override void RequestResume(Action continueResume) => throw new NotImplementedException(); + + public override void CancelResume() => throw new NotImplementedException(); + } + } + + public class TestHitObject : HitObject + { + public TestHitObject(HitWindows hitWindows) + { + HitWindows = hitWindows; + HitWindows.SetDifficulty(0.5f); + } + + public new void AddNested(HitObject nested) => base.AddNested(nested); + } +} From 1facdcf4838c4948f03d58c8b23fcf8dd5cd8759 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 23:23:24 +0200 Subject: [PATCH 1288/1621] Apply changes to tests --- .../NonVisual/FirstAvailableHitWindowTest.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs index 92766d8334..2f1ad194bb 100644 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs @@ -23,51 +23,47 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestResultIfOnlyParentHitWindowIsEmpty() { - var testObject = new TestHitObject(hitWindows: HitWindows.Empty); - HitObject nested = new TestHitObject(hitWindows: new HitWindows()); + var testObject = new TestHitObject(HitWindows.Empty); + HitObject nested = new TestHitObject(new HitWindows()); testObject.AddNested(nested); testDrawableRuleset.HitObjects = new List { testObject }; - // If the parent window is empty, but its nested object isn't, return the nested object Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, nested.HitWindows); } [Test] public void TestResultIfParentHitWindowsIsNotEmpty() { - var testObject = new TestHitObject(hitWindows: new HitWindows()); - HitObject nested = new TestHitObject(hitWindows: new HitWindows()); + var testObject = new TestHitObject(new HitWindows()); + HitObject nested = new TestHitObject(new HitWindows()); testObject.AddNested(nested); testDrawableRuleset.HitObjects = new List { testObject }; - // If the parent window is not empty, return that immediately Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, testObject.HitWindows); } [Test] public void TestResultIfParentAndChildHitWindowsAreEmpty() { - var firstObject = new TestHitObject(hitWindows: HitWindows.Empty); - HitObject nested = new TestHitObject(hitWindows: HitWindows.Empty); + var firstObject = new TestHitObject(HitWindows.Empty); + HitObject nested = new TestHitObject(HitWindows.Empty); firstObject.AddNested(nested); - var secondObject = new TestHitObject(hitWindows: new HitWindows()); + var secondObject = new TestHitObject(new HitWindows()); testDrawableRuleset.HitObjects = new List { firstObject, secondObject }; - // If the parent and child windows are empty, return the next object if window isn't empty Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, secondObject.HitWindows); } [Test] public void TestResultIfAllHitWindowsAreEmpty() { - var firstObject = new TestHitObject(hitWindows: HitWindows.Empty); - HitObject nested = new TestHitObject(hitWindows: HitWindows.Empty); + var firstObject = new TestHitObject(HitWindows.Empty); + HitObject nested = new TestHitObject(HitWindows.Empty); firstObject.AddNested(nested); testDrawableRuleset.HitObjects = new List { firstObject }; - // If all windows are empty, this should return null Assert.IsNull(testDrawableRuleset.FirstAvailableHitWindows); } From 216e52d6d0333d730ef13efdbd09534ee0c59e62 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 4 Jul 2021 23:24:17 +0200 Subject: [PATCH 1289/1621] Avoid using single letter variable names --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index cccd73f19f..daf46dcdcc 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -489,15 +489,15 @@ namespace osu.Game.Rulesets.UI { get { - foreach (var h in Objects) + foreach (var hitObject in Objects) { - if (h.HitWindows.WindowFor(HitResult.Miss) > 0) - return h.HitWindows; + if (hitObject.HitWindows.WindowFor(HitResult.Miss) > 0) + return hitObject.HitWindows; - foreach (var n in h.NestedHitObjects) + foreach (var nested in hitObject.NestedHitObjects) { - if (n.HitWindows.WindowFor(HitResult.Miss) > 0) - return n.HitWindows; + if (nested.HitWindows.WindowFor(HitResult.Miss) > 0) + return nested.HitWindows; } } From cc877f88e2fd97dcd0556e233067a7cc0e2b9d57 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 10:13:01 +0900 Subject: [PATCH 1290/1621] Fix inspection (create a new ruleset every time) --- osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs index 2f1ad194bb..aed2a5c122 100644 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs @@ -18,7 +18,13 @@ namespace osu.Game.Tests.NonVisual { public class FirstAvailableHitWindowsTest { - private TestDrawableRuleset testDrawableRuleset = new TestDrawableRuleset(); + private TestDrawableRuleset testDrawableRuleset; + + [SetUp] + public void Setup() + { + testDrawableRuleset = new TestDrawableRuleset(); + } [Test] public void TestResultIfOnlyParentHitWindowIsEmpty() From eecf4af0293384a6fe3c0da24eaf322529ba7951 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 5 Jul 2021 09:16:01 +0800 Subject: [PATCH 1291/1621] Rename `getSliderBoundingBox` and add comments --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index abc7db0a82..4c856ef4a0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -148,18 +148,19 @@ namespace osu.Game.Rulesets.Osu.Mods /// The that this slider has been shifted by. private Vector2 moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) { - var boundingBox = getSliderBoundingBox(slider); + var area = getSliderPlacementArea(slider); var prevPosition = slider.Position; + // Clamp slider position to the placement area // If the slider is larger than the playfield, force it to stay at the original position - var newX = boundingBox.Width < 0 + var newX = area.Width < 0 ? currentObjectInfo.PositionOriginal.X - : Math.Clamp(slider.Position.X, boundingBox.Left, boundingBox.Right); + : Math.Clamp(slider.Position.X, area.Left, area.Right); - var newY = boundingBox.Height < 0 + var newY = area.Height < 0 ? currentObjectInfo.PositionOriginal.Y - : Math.Clamp(slider.Position.Y, boundingBox.Top, boundingBox.Bottom); + : Math.Clamp(slider.Position.Y, area.Top, area.Bottom); slider.Position = new Vector2(newX, newY); @@ -191,15 +192,21 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Calculates the bounding box of a 's position for the slider to be fully inside of the playfield. + /// Calculates a that includes all possible positions of the slider such that + /// the entire slider is inside the playfield. /// - private RectangleF getSliderBoundingBox(Slider slider) + /// + /// If the slider is larger than the playfield, the returned may have negative width/height. + /// + private RectangleF getSliderPlacementArea(Slider slider) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); + // Initially, assume that the slider can be placed anywhere in the playfield var box = new RectangleF(Vector2.Zero, OsuPlayfield.BASE_SIZE); + // Then narrow down the area with each path position foreach (var pos in pathPositions) { // Reduce Width and Height accordingly after increasing X and Y @@ -216,6 +223,7 @@ namespace osu.Game.Rulesets.Osu.Mods box.Height = Math.Min(box.Height, OsuPlayfield.BASE_SIZE.Y - pos.Y - box.Y); } + // Reduce the area by slider radius, so that the slider fits inside the playfield completely var radius = (float)slider.Radius; box.X += radius; From f510ef9153618d238ea534edb943b1c732b95690 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 10:49:55 +0900 Subject: [PATCH 1292/1621] Move `previousContext` assign within `lock` to make things look safer Not an actual requirement, but no harm. --- osu.Game/Database/RealmContextFactory.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index b0241fb93c..ed3dc01f15 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; +using System.Diagnostics; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Development; @@ -166,11 +167,15 @@ namespace osu.Game.Database private void flushContexts() { Logger.Log(@"Flushing realm contexts...", LoggingTarget.Database); + Debug.Assert(blockingLock.CurrentCount == 0); - var previousContext = context; + Realm previousContext; lock (updateContextLock) + { + previousContext = context; context = null; + } // wait for all threaded usages to finish while (active_usages.Value > 0) From bfb603cfebed6b77b192c87309365f108a6d8d11 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 5 Jul 2021 09:51:24 +0800 Subject: [PATCH 1293/1621] Change the test for unimplemented mod to use a mock ruleset and mod --- .../TestSceneModSelectOverlay.cs | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index df8ef92a05..07bd28ac64 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -109,8 +109,6 @@ namespace osu.Game.Tests.Visual.UserInterface var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); - var targetMod = conversionMods.FirstOrDefault(m => m is OsuModTarget); - var easy = easierMods.FirstOrDefault(m => m is OsuModEasy); var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock); @@ -118,8 +116,6 @@ namespace osu.Game.Tests.Visual.UserInterface testMultiMod(doubleTimeMod); testIncompatibleMods(easy, hardRock); testDeselectAll(easierMods.Where(m => !(m is MultiMod))); - - testUnimplementedMod(targetMod); } [Test] @@ -249,6 +245,19 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("DT + HD still selected", () => modSelect.ChildrenOfType().Count(b => b.Selected) == 2); } + [Test] + public void TestUnimplementedModIsUnselectable() + { + var testRuleset = new TestUnimplementedModOsuRuleset(); + changeTestRuleset(testRuleset.RulesetInfo); + + var conversionMods = testRuleset.GetModsFor(ModType.Conversion); + + var unimplementedMod = conversionMods.FirstOrDefault(m => m is TestUnimplementedMod); + + testUnimplementedMod(unimplementedMod); + } + private void testSingleMod(Mod mod) { selectNext(mod); @@ -343,6 +352,12 @@ namespace osu.Game.Tests.Visual.UserInterface waitForLoad(); } + private void changeTestRuleset(RulesetInfo rulesetInfo) + { + AddStep($"change ruleset to {rulesetInfo.Name}", () => { Ruleset.Value = rulesetInfo; }); + waitForLoad(); + } + private void waitForLoad() => AddUntilStep("wait for icons to load", () => modSelect.AllLoaded); @@ -401,5 +416,24 @@ namespace osu.Game.Tests.Visual.UserInterface { protected override bool Stacked => false; } + + private class TestUnimplementedMod : Mod + { + public override string Name => "Unimplemented mod"; + public override string Acronym => "UM"; + public override string Description => "A mod that is not implemented."; + public override double ScoreMultiplier => 1; + public override ModType Type => ModType.Conversion; + } + + private class TestUnimplementedModOsuRuleset : OsuRuleset + { + public override IEnumerable GetModsFor(ModType type) + { + if (type == ModType.Conversion) return base.GetModsFor(type).Concat(new[] { new TestUnimplementedMod() }); + + return base.GetModsFor(type); + } + } } } From 3c371404264a9f792f28ace262e6d7ebe8296ca1 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 5 Jul 2021 09:52:13 +0800 Subject: [PATCH 1294/1621] Remove an unused local variable --- osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 07bd28ac64..4a1d90d871 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -103,7 +103,6 @@ namespace osu.Game.Tests.Visual.UserInterface var easierMods = osu.GetModsFor(ModType.DifficultyReduction); var harderMods = osu.GetModsFor(ModType.DifficultyIncrease); - var conversionMods = osu.GetModsFor(ModType.Conversion); var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); From 1e4beddd2d37ef522045a35c1bb263b46e4579c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 15:41:48 +0900 Subject: [PATCH 1295/1621] Disable foreign key enforcing at an sqlite level --- osu.Game/Database/OsuDbContext.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index e0c0f56cb3..68d186c65d 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -77,6 +77,9 @@ namespace osu.Game.Database { cmd.CommandText = "PRAGMA journal_mode=WAL;"; cmd.ExecuteNonQuery(); + + cmd.CommandText = "PRAGMA foreign_keys=OFF;"; + cmd.ExecuteNonQuery(); } } catch From 96c0ab8dedbc5702977f07785643e8eb2a5d41c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 17:11:59 +0900 Subject: [PATCH 1296/1621] Adjust last frame position when not waiting --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 7b0cf651c8..a9b5a2d0ef 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -249,6 +249,18 @@ namespace osu.Game.Rulesets.Osu.Replays ((lastPosition - targetPos).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough timeDifference >= 266)) // ... or the beats are slow enough to tap anyway. { + OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; + + // The last frame may be a key-up frame if it shares a position with the second-last frame. + // If it is a key-up frame and its time occurs after the "wait time" (i.e. there was no wait period), adjust its position to begin eased movement instantaneously. + if (lastLastFrame?.Position == lastFrame.Position && lastFrame.Time >= waitTime) + { + // [lastLastFrame] ... [lastFrame] ... [current frame] + // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. + lastFrame.Position = Interpolation.ValueAt(lastFrame.Time, lastFrame.Position, targetPos, lastLastFrame.Time, h.StartTime, easing); + lastPosition = lastFrame.Position; + } + // Perform eased movement for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time)) { From 6a2c0f772e84cd9dddb3ca9b8f35295956b6d990 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 17:22:48 +0900 Subject: [PATCH 1297/1621] Always apply easing, adjust heuristic a bit --- .../Replays/OsuAutoGenerator.cs | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index a9b5a2d0ef..0df5909d50 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -240,40 +240,33 @@ namespace osu.Game.Rulesets.Osu.Replays AddFrameToReplay(lastFrame); } - Vector2 lastPosition = lastFrame.Position; - double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime); - // Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up. - if (timeDifference > 0 && // Sanity checks - ((lastPosition - targetPos).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough - timeDifference >= 266)) // ... or the beats are slow enough to tap anyway. + OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; + + // The last frame may be a key-up frame if it shares a position with the second-last frame. + // If it is a key-up frame and its time occurs after the "wait time" (i.e. there was no wait period), adjust its position to begin eased movement instantaneously. + if (lastLastFrame?.Position == lastFrame.Position && lastFrame.Time >= waitTime) { - OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; - - // The last frame may be a key-up frame if it shares a position with the second-last frame. - // If it is a key-up frame and its time occurs after the "wait time" (i.e. there was no wait period), adjust its position to begin eased movement instantaneously. - if (lastLastFrame?.Position == lastFrame.Position && lastFrame.Time >= waitTime) - { - // [lastLastFrame] ... [lastFrame] ... [current frame] - // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. - lastFrame.Position = Interpolation.ValueAt(lastFrame.Time, lastFrame.Position, targetPos, lastLastFrame.Time, h.StartTime, easing); - lastPosition = lastFrame.Position; - } - - // Perform eased movement - for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time)) - { - Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); - AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions }); - } - - buttonIndex = 0; + // [lastLastFrame] ... [lastFrame] ... [current frame] + // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. + lastFrame.Position = Interpolation.ValueAt(lastFrame.Time, lastFrame.Position, targetPos, lastLastFrame.Time, h.StartTime, easing); } - else + + Vector2 lastPosition = lastFrame.Position; + + // Perform eased movement. + for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time)) { + Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); + AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions }); + } + + // Start alternating once the time separation is too small (equivalent 120BPM @ 1/4 divisor). + if (timeDifference > 0 && timeDifference < 125) buttonIndex++; - } + else + buttonIndex = 0; } /// From 12ca845e55d7f0bf1fe4594a3f0330307b39a780 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 17:24:23 +0900 Subject: [PATCH 1298/1621] Use marker class (cleanup) --- .../Replays/OsuAutoGenerator.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 0df5909d50..37d3e9d57a 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -233,20 +233,20 @@ namespace osu.Game.Rulesets.Osu.Replays // Wait until Auto could "see and react" to the next note. double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - getReactionTime(h.StartTime - h.TimePreempt)); + bool hasWaited = false; if (waitTime > lastFrame.Time) { lastFrame = new OsuReplayFrame(waitTime, lastFrame.Position) { Actions = lastFrame.Actions }; + hasWaited = true; AddFrameToReplay(lastFrame); } double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime); - OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; - // The last frame may be a key-up frame if it shares a position with the second-last frame. - // If it is a key-up frame and its time occurs after the "wait time" (i.e. there was no wait period), adjust its position to begin eased movement instantaneously. - if (lastLastFrame?.Position == lastFrame.Position && lastFrame.Time >= waitTime) + // If the last frame is a key-up frame and there has been no wait period, adjust the last frame's position such that it begins eased movement instantaneously. + if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited) { // [lastLastFrame] ... [lastFrame] ... [current frame] // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. @@ -289,7 +289,7 @@ namespace osu.Game.Rulesets.Osu.Replays // TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime. double hEndTime = h.GetEndTime() + KEY_UP_DELAY; int endDelay = h is Spinner ? 1 : 0; - var endFrame = new OsuReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y)); + var endFrame = new OsuKeyUpReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y)); // Decrement because we want the previous frame, not the next one int index = FindInsertionIndex(startFrame) - 1; @@ -386,5 +386,13 @@ namespace osu.Game.Rulesets.Osu.Replays } #endregion + + private class OsuKeyUpReplayFrame : OsuReplayFrame + { + public OsuKeyUpReplayFrame(double time, Vector2 position) + : base(time, position) + { + } + } } } From 229bba14e6261142692bda7bba8bf5cdb7f325eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 17:45:08 +0900 Subject: [PATCH 1299/1621] Fix master clock becoming incorrectly paused when all spectator players are too far ahead --- .../OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index 94278a47b6..cf0dfbb585 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -130,6 +130,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate // This is a quiet case in which the catchup is done by the master clock, so IsCatchingUp is not set on the player clock. if (timeDelta < -SYNC_TARGET) { + // Importantly, set the clock to a non-catchup state. if this isn't done, updateMasterState may incorrectly pause the master clock + // when it is required to be running (ie. if all players are ahead of the master). + clock.IsCatchingUp = false; clock.Stop(); continue; } From 2b8efe21caeb6c0450388749089183a184d42cf8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 17:52:10 +0900 Subject: [PATCH 1300/1621] Don't ease with 0 time difference --- .../Replays/OsuAutoGenerator.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 37d3e9d57a..20a5829274 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -245,21 +245,25 @@ namespace osu.Game.Rulesets.Osu.Replays double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime); OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; - // If the last frame is a key-up frame and there has been no wait period, adjust the last frame's position such that it begins eased movement instantaneously. - if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited) + if (timeDifference > 0) { - // [lastLastFrame] ... [lastFrame] ... [current frame] - // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. - lastFrame.Position = Interpolation.ValueAt(lastFrame.Time, lastFrame.Position, targetPos, lastLastFrame.Time, h.StartTime, easing); - } + // If the last frame is a key-up frame and there has been no wait period, adjust the last frame's position such that it begins eased movement instantaneously. + if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited + && lastFrame.Time > lastLastFrame.Time) // + { + // [lastLastFrame] ... [lastFrame] ... [current frame] + // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. + lastFrame.Position = Interpolation.ValueAt(lastFrame.Time, lastFrame.Position, targetPos, lastLastFrame.Time, h.StartTime, easing); + } - Vector2 lastPosition = lastFrame.Position; + Vector2 lastPosition = lastFrame.Position; - // Perform eased movement. - for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time)) - { - Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); - AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions }); + // Perform eased movement. + for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time)) + { + Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); + AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions }); + } } // Start alternating once the time separation is too small (equivalent 120BPM @ 1/4 divisor). From 7645da7d379b19b6d9a106efd4fc463d0eaccc69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 18:20:04 +0900 Subject: [PATCH 1301/1621] Fix incorrect filename --- .../NonVisual/FirstAvailableHitWindowTest.cs | 104 ---------------- .../NonVisual/FirstAvailableHitWindowsTest.cs | 112 ++++++++++++++++++ 2 files changed, 112 insertions(+), 104 deletions(-) create mode 100644 osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs index aed2a5c122..51e15d2426 100644 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs @@ -1,115 +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.Diagnostics.CodeAnalysis; -using NUnit.Framework; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Scoring; namespace osu.Game.Tests.NonVisual { - public class FirstAvailableHitWindowsTest - { - private TestDrawableRuleset testDrawableRuleset; - - [SetUp] - public void Setup() - { - testDrawableRuleset = new TestDrawableRuleset(); - } - - [Test] - public void TestResultIfOnlyParentHitWindowIsEmpty() - { - var testObject = new TestHitObject(HitWindows.Empty); - HitObject nested = new TestHitObject(new HitWindows()); - testObject.AddNested(nested); - testDrawableRuleset.HitObjects = new List { testObject }; - - Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, nested.HitWindows); - } - - [Test] - public void TestResultIfParentHitWindowsIsNotEmpty() - { - var testObject = new TestHitObject(new HitWindows()); - HitObject nested = new TestHitObject(new HitWindows()); - testObject.AddNested(nested); - testDrawableRuleset.HitObjects = new List { testObject }; - - Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, testObject.HitWindows); - } - - [Test] - public void TestResultIfParentAndChildHitWindowsAreEmpty() - { - var firstObject = new TestHitObject(HitWindows.Empty); - HitObject nested = new TestHitObject(HitWindows.Empty); - firstObject.AddNested(nested); - - var secondObject = new TestHitObject(new HitWindows()); - testDrawableRuleset.HitObjects = new List { firstObject, secondObject }; - - Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, secondObject.HitWindows); - } - - [Test] - public void TestResultIfAllHitWindowsAreEmpty() - { - var firstObject = new TestHitObject(HitWindows.Empty); - HitObject nested = new TestHitObject(HitWindows.Empty); - firstObject.AddNested(nested); - - testDrawableRuleset.HitObjects = new List { firstObject }; - - Assert.IsNull(testDrawableRuleset.FirstAvailableHitWindows); - } - - [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] - private class TestDrawableRuleset : DrawableRuleset - { - public List HitObjects; - public override IEnumerable Objects => HitObjects; - - public override event Action NewResult; - public override event Action RevertResult; - - public override Playfield Playfield { get; } - public override Container Overlays { get; } - public override Container FrameStableComponents { get; } - public override IFrameStableClock FrameStableClock { get; } - internal override bool FrameStablePlayback { get; set; } - public override IReadOnlyList Mods { get; } - - public override double GameplayStartTime { get; } - public override GameplayCursorContainer Cursor { get; } - - public TestDrawableRuleset() - : base(new OsuRuleset()) - { - // won't compile without this. - NewResult?.Invoke(null); - RevertResult?.Invoke(null); - } - - public override void SetReplayScore(Score replayScore) => throw new NotImplementedException(); - - public override void SetRecordTarget(Score score) => throw new NotImplementedException(); - - public override void RequestResume(Action continueResume) => throw new NotImplementedException(); - - public override void CancelResume() => throw new NotImplementedException(); - } - } - public class TestHitObject : HitObject { public TestHitObject(HitWindows hitWindows) diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs new file mode 100644 index 0000000000..d4364b15e7 --- /dev/null +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs @@ -0,0 +1,112 @@ +// 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.CodeAnalysis; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; + +namespace osu.Game.Tests.NonVisual +{ + public class FirstAvailableHitWindowsTest + { + private TestDrawableRuleset testDrawableRuleset; + + [SetUp] + public void Setup() + { + testDrawableRuleset = new TestDrawableRuleset(); + } + + [Test] + public void TestResultIfOnlyParentHitWindowIsEmpty() + { + var testObject = new TestHitObject(HitWindows.Empty); + HitObject nested = new TestHitObject(new HitWindows()); + testObject.AddNested(nested); + testDrawableRuleset.HitObjects = new List { testObject }; + + Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, nested.HitWindows); + } + + [Test] + public void TestResultIfParentHitWindowsIsNotEmpty() + { + var testObject = new TestHitObject(new HitWindows()); + HitObject nested = new TestHitObject(new HitWindows()); + testObject.AddNested(nested); + testDrawableRuleset.HitObjects = new List { testObject }; + + Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, testObject.HitWindows); + } + + [Test] + public void TestResultIfParentAndChildHitWindowsAreEmpty() + { + var firstObject = new TestHitObject(HitWindows.Empty); + HitObject nested = new TestHitObject(HitWindows.Empty); + firstObject.AddNested(nested); + + var secondObject = new TestHitObject(new HitWindows()); + testDrawableRuleset.HitObjects = new List { firstObject, secondObject }; + + Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, secondObject.HitWindows); + } + + [Test] + public void TestResultIfAllHitWindowsAreEmpty() + { + var firstObject = new TestHitObject(HitWindows.Empty); + HitObject nested = new TestHitObject(HitWindows.Empty); + firstObject.AddNested(nested); + + testDrawableRuleset.HitObjects = new List { firstObject }; + + Assert.IsNull(testDrawableRuleset.FirstAvailableHitWindows); + } + + [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] + private class TestDrawableRuleset : DrawableRuleset + { + public List HitObjects; + public override IEnumerable Objects => HitObjects; + + public override event Action NewResult; + public override event Action RevertResult; + + public override Playfield Playfield { get; } + public override Container Overlays { get; } + public override Container FrameStableComponents { get; } + public override IFrameStableClock FrameStableClock { get; } + internal override bool FrameStablePlayback { get; set; } + public override IReadOnlyList Mods { get; } + + public override double GameplayStartTime { get; } + public override GameplayCursorContainer Cursor { get; } + + public TestDrawableRuleset() + : base(new OsuRuleset()) + { + // won't compile without this. + NewResult?.Invoke(null); + RevertResult?.Invoke(null); + } + + public override void SetReplayScore(Score replayScore) => throw new NotImplementedException(); + + public override void SetRecordTarget(Score score) => throw new NotImplementedException(); + + public override void RequestResume(Action continueResume) => throw new NotImplementedException(); + + public override void CancelResume() => throw new NotImplementedException(); + } + } +} From 695af31c58d696f4cd6d5897ea56d1c80e3f3167 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 18:38:02 +0900 Subject: [PATCH 1302/1621] Start alternating at 225BPM as previously --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 20a5829274..68743c2a86 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -266,8 +266,8 @@ namespace osu.Game.Rulesets.Osu.Replays } } - // Start alternating once the time separation is too small (equivalent 120BPM @ 1/4 divisor). - if (timeDifference > 0 && timeDifference < 125) + // Start alternating once the time separation is too small (faster than ~225BPM). + if (timeDifference > 0 && timeDifference < 266) buttonIndex++; else buttonIndex = 0; From 7d6ab08bb3c1bd870216351379587f66482f62fb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 18:49:09 +0900 Subject: [PATCH 1303/1621] Remove unnecessary conditional --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 68743c2a86..73dbcbd501 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -248,8 +248,7 @@ namespace osu.Game.Rulesets.Osu.Replays if (timeDifference > 0) { // If the last frame is a key-up frame and there has been no wait period, adjust the last frame's position such that it begins eased movement instantaneously. - if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited - && lastFrame.Time > lastLastFrame.Time) // + if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited) { // [lastLastFrame] ... [lastFrame] ... [current frame] // We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position. From 8b7ccdc8b5c591120f09b30baed07c919082a864 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Jul 2021 18:51:23 +0900 Subject: [PATCH 1304/1621] Adjust comment --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 73dbcbd501..b88bf9108b 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -257,7 +257,7 @@ namespace osu.Game.Rulesets.Osu.Replays Vector2 lastPosition = lastFrame.Position; - // Perform eased movement. + // Perform the rest of the eased movement until the target position is reached. for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time)) { Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); From cd2916f778c7c740793a1b72502461269851368e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 18:56:59 +0900 Subject: [PATCH 1305/1621] Fix remaining incorrect filename --- .../NonVisual/FirstAvailableHitWindowTest.cs | 19 ------------------- .../NonVisual/FirstAvailableHitWindowsTest.cs | 11 +++++++++++ 2 files changed, 11 insertions(+), 19 deletions(-) delete mode 100644 osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs deleted file mode 100644 index 51e15d2426..0000000000 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowTest.cs +++ /dev/null @@ -1,19 +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.Objects; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Tests.NonVisual -{ - public class TestHitObject : HitObject - { - public TestHitObject(HitWindows hitWindows) - { - HitWindows = hitWindows; - HitWindows.SetDifficulty(0.5f); - } - - public new void AddNested(HitObject nested) => base.AddNested(nested); - } -} diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs index d4364b15e7..97105b6b6a 100644 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs @@ -108,5 +108,16 @@ namespace osu.Game.Tests.NonVisual public override void CancelResume() => throw new NotImplementedException(); } + + public class TestHitObject : HitObject + { + public TestHitObject(HitWindows hitWindows) + { + HitWindows = hitWindows; + HitWindows.SetDifficulty(0.5f); + } + + public new void AddNested(HitObject nested) => base.AddNested(nested); + } } } From d3bb4ddbee09ebcab963add6fcce38945d01b99f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 5 Jul 2021 19:05:49 +0900 Subject: [PATCH 1306/1621] Add an ad-hoc way to provide dependency to children --- .../Visual/DependencyProvidingContainer.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 osu.Game/Tests/Visual/DependencyProvidingContainer.cs diff --git a/osu.Game/Tests/Visual/DependencyProvidingContainer.cs b/osu.Game/Tests/Visual/DependencyProvidingContainer.cs new file mode 100644 index 0000000000..cd3ae1123b --- /dev/null +++ b/osu.Game/Tests/Visual/DependencyProvidingContainer.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Tests.Visual +{ + /// + /// A which providing ad-hoc dependencies to the child drawables. + /// + /// To provide a dependency, specify the dependency type to , then specify the dependency value to either or . + /// For each type specified in , the first value compatible with the type is selected and provided to the children. + /// + /// + /// + /// The and values of the dependencies must be set while this is not loaded. + /// + public class DependencyProvidingContainer : Container + { + /// + /// The types of the dependencies provided to the children. + /// + // TODO: should be an init-only property when C# 9 + public Type[] Types { get; set; } = Array.Empty(); + + /// + /// The dependency values provided to the children. + /// + public object[] Values { get; set; } = Array.Empty(); + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencyContainer = new DependencyContainer(base.CreateChildDependencies(parent)); + + foreach (var type in Types) + { + object value = Values.FirstOrDefault(v => type.IsInstanceOfType(v)) ?? + Children.FirstOrDefault(d => type.IsInstanceOfType(d)) ?? + throw new InvalidOperationException($"The type {type} is specified in this {nameof(DependencyProvidingContainer)}, but no corresponding value is provided."); + + dependencyContainer.CacheAs(type, value); + } + + return dependencyContainer; + } + } +} From 5a0a223b1b96d2f10630d888d54f68e06a9acb95 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 5 Jul 2021 19:07:29 +0900 Subject: [PATCH 1307/1621] Use `DependencyProvidingContainer` in `TestSceneCatcher` --- .../TestSceneCatcher.cs | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 8359657f84..0cf5e2b7a0 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -31,22 +31,9 @@ namespace osu.Game.Rulesets.Catch.Tests [Resolved] private OsuConfigManager config { get; set; } - [Cached] - private readonly DroppedObjectContainer droppedObjectContainer; - - private readonly Container trailContainer; - private TestCatcher catcher; - public TestSceneCatcher() - { - Add(trailContainer = new Container - { - Anchor = Anchor.Centre, - Depth = -1 - }); - Add(droppedObjectContainer = new DroppedObjectContainer()); - } + private DroppedObjectContainer droppedObjectContainer; [SetUp] public void SetUp() => Schedule(() => @@ -56,13 +43,24 @@ namespace osu.Game.Rulesets.Catch.Tests CircleSize = 0, }; - if (catcher != null) - Remove(catcher); - - Add(catcher = new TestCatcher(trailContainer, difficulty) + var trailContainer = new Container { + Anchor = Anchor.Centre, + }; + Child = new DependencyProvidingContainer + { + Types = new[] + { + typeof(DroppedObjectContainer), + }, + Children = new Drawable[] + { + droppedObjectContainer = new DroppedObjectContainer(), + catcher = new TestCatcher(trailContainer, difficulty), + trailContainer + }, Anchor = Anchor.Centre - }); + }; }); [Test] From 9c2fed4806217e05312892a2e339bce6cebda0eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 19:10:27 +0900 Subject: [PATCH 1308/1621] Move setup steps to `SetUpSteps` and add empty test case --- .../Multiplayer/TestSceneMultiplayer.cs | 70 +++++++++---------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index c93640e7b5..7f3e17985e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -39,11 +39,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private TestMultiplayer multiplayerScreen; private TestMultiplayerClient client; - public TestSceneMultiplayer() - { - loadMultiplayer(); - } - [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { @@ -51,18 +46,43 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); } - [SetUp] - public void Setup() => Schedule(() => + public override void SetUpSteps() { - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); - importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); - }); + base.SetUpSteps(); + + AddStep("import beatmap", () => + { + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + }); + + AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer()); + + AddStep("load dependencies", () => + { + client = new TestMultiplayerClient(multiplayerScreen.RoomManager); + + // The screen gets suspended so it stops receiving updates. + Child = client; + + LoadScreen(dependenciesScreen = new DependenciesScreen(client)); + }); + + AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded); + + AddStep("load multiplayer", () => LoadScreen(multiplayerScreen)); + AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded); + } + + [Test] + public void TestEmpty() + { + // used to test the flow of multiplayer from visual tests. + } [Test] public void TestUserSetToIdleWhenBeatmapDeleted() { - loadMultiplayer(); - createRoom(() => new Room { Name = { Value = "Test Room" }, @@ -85,8 +105,6 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap() { - loadMultiplayer(); - createRoom(() => new Room { Name = { Value = "Test Room" }, @@ -123,8 +141,6 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestLocalPlayStartsWhileSpectatingWhenBeatmapBecomesAvailable() { - loadMultiplayer(); - createRoom(() => new Room { Name = { Value = "Test Room" }, @@ -167,8 +183,6 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestLeaveNavigation() { - loadMultiplayer(); - createRoom(() => new Room { Name = { Value = "Test Room" }, @@ -227,26 +241,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => client.Room != null); } - private void loadMultiplayer() - { - AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer()); - - AddStep("load dependencies", () => - { - client = new TestMultiplayerClient(multiplayerScreen.RoomManager); - - // The screen gets suspended so it stops receiving updates. - Child = client; - - LoadScreen(dependenciesScreen = new DependenciesScreen(client)); - }); - - AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded); - - AddStep("load multiplayer", () => LoadScreen(multiplayerScreen)); - AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded); - } - /// /// Used for the sole purpose of adding as a resolvable dependency. /// From 10e7c846e55a1940ce9230768de1ed0af2f0e45d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 19:41:00 +0900 Subject: [PATCH 1309/1621] Add local `UserLookupCache` to avoid online requests being fired from multiplayer tests --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 7f3e17985e..2bb3129f68 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -11,6 +11,7 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -39,6 +40,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private TestMultiplayer multiplayerScreen; private TestMultiplayerClient client; + [Cached(typeof(UserLookupCache))] + private UserLookupCache lookupCache = new TestUserLookupCache(); + [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { From 9c311a6d8a60db4a3318478b007b4e7e09dc78c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 19:56:16 +0900 Subject: [PATCH 1310/1621] Add ability to lookup multiple users at once to `UserLookupCache` --- osu.Game/Database/UserLookupCache.cs | 24 ++++++++++++++++++++ osu.Game/Screens/Spectate/SpectatorScreen.cs | 21 +---------------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index 19cc211709..e5fd9c82ac 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -27,6 +27,30 @@ namespace osu.Game.Database [ItemCanBeNull] public Task GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token); + /// + /// Perform an API lookup on the specified users, populating a model. + /// + /// The users to lookup. + /// An optional cancellation token. + /// . + public Task GetUsersAsync(int[] userIds, CancellationToken token = default) + { + var userLookupTasks = new List>(); + + foreach (var u in userIds) + { + userLookupTasks.Add(GetUserAsync(u, token).ContinueWith(task => + { + if (!task.IsCompletedSuccessfully) + return null; + + return task.Result; + }, token)); + } + + return Task.WhenAll(userLookupTasks); + } + protected override async Task ComputeValueAsync(int lookup, CancellationToken token = default) => await queryUser(lookup).ConfigureAwait(false); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 8fc9222f59..cd386a3d48 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -61,7 +60,7 @@ namespace osu.Game.Screens.Spectate { base.LoadComplete(); - getAllUsers().ContinueWith(users => Schedule(() => + userLookupCache.GetUsersAsync(userIds.ToArray()).ContinueWith(users => Schedule(() => { foreach (var u in users.Result) userMap[u.Id] = u; @@ -77,24 +76,6 @@ namespace osu.Game.Screens.Spectate })); } - private Task getAllUsers() - { - var userLookupTasks = new List>(); - - foreach (var u in userIds) - { - userLookupTasks.Add(userLookupCache.GetUserAsync(u).ContinueWith(task => - { - if (!task.IsCompletedSuccessfully) - return null; - - return task.Result; - })); - } - - return Task.WhenAll(userLookupTasks); - } - private void beatmapUpdated(ValueChangedEvent> e) { if (!e.NewValue.TryGetTarget(out var beatmapSet)) From 77adf687c6946471301a31a0c52e2857fe4cd5a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 19:56:36 +0900 Subject: [PATCH 1311/1621] Consume ability to lookup multiple users in `MultiplayerGameplayLeaderboard` Avoids syncrhonously blocking on asynchronous operations (which was leading to LCA starvation in tests). --- .../Multiplayer/MultiplayerPlayer.cs | 4 +-- .../HUD/MultiplayerGameplayLeaderboard.cs | 26 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 1bbe49a705..043cce4630 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -125,9 +125,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { const float padding = 44; // enough margin to avoid the hit error display. - leaderboard.Position = new Vector2( - padding, - padding + HUDOverlay.TopScoringElementsHeight); + leaderboard.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight); } private void onMatchStarted() => Scheduler.Add(() => diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index c3bfe19b29..45f871ed13 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -53,22 +53,22 @@ namespace osu.Game.Screens.Play.HUD { scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); - foreach (var userId in playingUsers) + userLookupCache.GetUsersAsync(playingUsers.ToArray()).ContinueWith(users => Schedule(() => { - // probably won't be required in the final implementation. - var resolvedUser = userLookupCache.GetUserAsync(userId).Result; + foreach (var user in users.Result) + { + var trackedUser = CreateUserData(user.Id, scoreProcessor); + trackedUser.ScoringMode.BindTo(scoringMode); - var trackedUser = CreateUserData(userId, scoreProcessor); - trackedUser.ScoringMode.BindTo(scoringMode); + var leaderboardScore = AddPlayer(user, user.Id == api.LocalUser.Value.Id); + leaderboardScore.Accuracy.BindTo(trackedUser.Accuracy); + leaderboardScore.TotalScore.BindTo(trackedUser.Score); + leaderboardScore.Combo.BindTo(trackedUser.CurrentCombo); + leaderboardScore.HasQuit.BindTo(trackedUser.UserQuit); - var leaderboardScore = AddPlayer(resolvedUser, resolvedUser?.Id == api.LocalUser.Value.Id); - leaderboardScore.Accuracy.BindTo(trackedUser.Accuracy); - leaderboardScore.TotalScore.BindTo(trackedUser.Score); - leaderboardScore.Combo.BindTo(trackedUser.CurrentCombo); - leaderboardScore.HasQuit.BindTo(trackedUser.UserQuit); - - UserScores[userId] = trackedUser; - } + UserScores[user.Id] = trackedUser; + } + })); } protected override void LoadComplete() From d247b8042e2ccc4f1e54e308887737c41259ee6e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 5 Jul 2021 20:05:08 +0900 Subject: [PATCH 1312/1621] Fix default skin catcher not flipping catcher plate When legacy beatmap skin is present but catcher is not provided, it was using the legacy setting (always false). --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index b6f6e91c29..5e744ec001 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -108,8 +108,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (config) { case CatchSkinConfiguration.FlipCatcherPlate: - // Always return `false` (don't flip catcher plate contents) for a legacy skin. - return (IBindable)new Bindable(); + // Don't flip catcher plate contents if the catcher is provided by this legacy skin. + if (GetDrawableComponent(new CatchSkinComponent(CatchSkinComponents.Catcher)) != null) + return (IBindable)new Bindable(); + + break; } break; From 459f9a04654da0e7142601efa74503ac0e9a99ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 21:30:24 +0900 Subject: [PATCH 1313/1621] Handle nulls and fix missing documentation --- osu.Game/Database/UserLookupCache.cs | 2 +- osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 3 +++ osu.Game/Screens/Spectate/SpectatorScreen.cs | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index e5fd9c82ac..13c37ddfe9 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -32,7 +32,7 @@ namespace osu.Game.Database /// /// The users to lookup. /// An optional cancellation token. - /// . + /// The populated users. May include null results for failed retrievals. public Task GetUsersAsync(int[] userIds, CancellationToken token = default) { var userLookupTasks = new List>(); diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 45f871ed13..a32aca51c5 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -57,6 +57,9 @@ namespace osu.Game.Screens.Play.HUD { foreach (var user in users.Result) { + if (user == null) + continue; + var trackedUser = CreateUserData(user.Id, scoreProcessor); trackedUser.ScoringMode.BindTo(scoringMode); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index cd386a3d48..b6eafe496f 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -63,7 +63,12 @@ namespace osu.Game.Screens.Spectate userLookupCache.GetUsersAsync(userIds.ToArray()).ContinueWith(users => Schedule(() => { foreach (var u in users.Result) + { + if (u == null) + continue; + userMap[u.Id] = u; + } playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); From f1014af2848b9875a9f9db1e60e13e7a110a410d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Jul 2021 21:35:36 +0900 Subject: [PATCH 1314/1621] Move `LoadComplete` content to run after user retrieval has completed Feels safer, I think. --- osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index a32aca51c5..0c13e87f9f 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -71,13 +71,13 @@ namespace osu.Game.Screens.Play.HUD UserScores[user.Id] = trackedUser; } + + prepareDataStreams(); })); } - protected override void LoadComplete() + private void prepareDataStreams() { - base.LoadComplete(); - // BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually.. foreach (int userId in playingUsers) { From 3fe875efb2a28edfcd88036e83e81cfa67624c48 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 5 Jul 2021 15:47:35 +0200 Subject: [PATCH 1315/1621] Add glow to focused meter --- osu.Game/Overlays/Volume/VolumeMeter.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 7132d8e3ea..9210001a3a 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -38,6 +38,8 @@ namespace osu.Game.Overlays.Volume private OsuSpriteText text; private BufferedContainer maxGlow; + private Container focusGlowContainer; + private Sample sample; private double sampleLastPlaybackTime; @@ -75,6 +77,25 @@ namespace osu.Game.Overlays.Volume Size = new Vector2(circleSize), Children = new Drawable[] { + focusGlowContainer = new CircularContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = meterColour.Opacity(0.5f), + Radius = 5, + Hollow = true, + } + }, new BufferedContainer { Alpha = 0.9f, @@ -312,11 +333,13 @@ namespace osu.Game.Overlays.Volume public void Focus() { this.ScaleTo(1.04f, transition_length, Easing.OutExpo); + focusGlowContainer.FadeIn(transition_length, Easing.OutExpo); } public void Unfocus() { this.ScaleTo(1f, transition_length, Easing.OutExpo); + focusGlowContainer.FadeOut(transition_length, Easing.OutExpo); } protected override bool OnHover(HoverEvent e) From fdfdfeecab2a4775f7b6b0733421112d14c4446b Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Mon, 5 Jul 2021 23:35:17 +0800 Subject: [PATCH 1316/1621] Suppress IDE0057 --- .editorconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index f4d7e08d08..bbe091a852 100644 --- a/.editorconfig +++ b/.editorconfig @@ -168,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:silent -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_index_operator = false:silent +csharp_style_prefer_range_operator = false:silent csharp_style_prefer_switch_expression = false:none #Supressing roslyn built-in analyzers From a4545051f2463fdeeff7567ba0687fe7c96388e4 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Mon, 5 Jul 2021 23:36:15 +0800 Subject: [PATCH 1317/1621] Suppress IDE0042 --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index bbe091a852..19bd89c52f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -157,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning #Style - variable declaration csharp_style_inlined_variable_declaration = true:warning -csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = false:silent #Style - other C# 7.x features dotnet_style_prefer_inferred_tuple_names = true:warning From e1c646b9b297397bbb02fed8acb6b8e70cfe4158 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Mon, 5 Jul 2021 23:52:39 +0800 Subject: [PATCH 1318/1621] Remove redundant arguments --- .../TestSceneHyperDashColouring.cs | 2 +- .../TestSceneGameplayCursor.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 6 ++--- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 4 ++-- .../Skinning/Default/DefaultSpinnerDisc.cs | 14 ++++++------ .../Skinning/Default/SliderBall.cs | 2 +- .../Skinning/Legacy/LegacyNewStyleSpinner.cs | 8 +++---- .../Skinning/Legacy/LegacyOldStyleSpinner.cs | 4 ++-- .../Skinning/Legacy/LegacySpinner.cs | 8 +++---- .../Objects/Drawables/DrawableSwell.cs | 2 +- .../Beatmaps/SliderEventGenerationTest.cs | 10 ++++----- .../TestSceneCompletionCancellation.cs | 5 ----- .../Gameplay/TestSceneSkinnableDrawable.cs | 2 +- .../Gameplay/TestSceneSpectatorPlayback.cs | 2 +- .../TestSceneMultiSpectatorScreen.cs | 4 ++-- .../TestSceneProfileSubsectionHeader.cs | 2 +- .../Screens/Gameplay/GameplayScreen.cs | 2 +- .../Containers/BeatSyncedContainer.cs | 2 +- .../Graphics/UserInterface/StarCounter.cs | 2 +- .../Requests/GetSpotlightRankingsRequest.cs | 2 +- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- .../Online/Leaderboards/LeaderboardScore.cs | 6 ++--- osu.Game/Overlays/MedalOverlay.cs | 4 ++-- osu.Game/Overlays/Mods/ModButton.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- osu.Game/Overlays/News/NewsHeader.cs | 2 +- .../Header/Components/PreviousUsernames.cs | 2 +- .../Overlays/Rankings/Tables/RankingsTable.cs | 2 +- .../Rulesets/Judgements/DrawableJudgement.cs | 2 +- osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 2 +- .../Objects/Drawables/DrawableHitObject.cs | 6 ++--- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- osu.Game/Screens/Menu/Disclaimer.cs | 2 +- osu.Game/Screens/Menu/IntroSequence.cs | 14 ++++++------ osu.Game/Screens/Menu/IntroTriangles.cs | 22 +++++++++---------- osu.Game/Screens/Menu/IntroWelcome.cs | 2 +- .../Match/MultiplayerMatchSettingsOverlay.cs | 2 +- .../PlaylistsMatchSettingsOverlay.cs | 2 +- osu.Game/Screens/Play/BreakOverlay.cs | 4 ++-- .../Screens/Play/HUD/LegacyComboCounter.cs | 10 ++++----- .../Expanded/ExpandedPanelMiddleContent.cs | 4 ++-- .../Expanded/Statistics/ComboStatistic.cs | 2 +- osu.Game/Screens/Ranking/ScorePanel.cs | 2 +- osu.Game/Screens/ScreenWhiteBox.cs | 2 +- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 2 +- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 50 files changed, 97 insertions(+), 102 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 7fa981d492..e7b0259ea2 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("finish hyper-dashing", () => { - catcherArea.MovableCatcher.SetHyperDashState(1); + catcherArea.MovableCatcher.SetHyperDashState(); catcherArea.MovableCatcher.FinishTransforms(); }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 78bb88322a..2326a0c391 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Tests { case OsuSkinConfiguration osuLookup: if (osuLookup == OsuSkinConfiguration.CursorCentre) - return SkinUtils.As(new BindableBool(false)); + return SkinUtils.As(new BindableBool()); break; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index ebf6f9dda7..636cd63c69 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -158,17 +158,17 @@ namespace osu.Game.Rulesets.Osu.Mods var firstObj = beatmap.HitObjects[0]; var startDelay = firstObj.StartTime - firstObj.TimePreempt; - using (BeginAbsoluteSequence(startDelay + break_close_late, true)) + using (BeginAbsoluteSequence(startDelay + break_close_late)) leaveBreak(); foreach (var breakInfo in beatmap.Breaks) { if (breakInfo.HasEffect) { - using (BeginAbsoluteSequence(breakInfo.StartTime - break_open_early, true)) + using (BeginAbsoluteSequence(breakInfo.StartTime - break_open_early)) { enterBreak(); - using (BeginDelayedSequence(breakInfo.Duration + break_open_early + break_close_late, true)) + using (BeginDelayedSequence(breakInfo.Duration + break_open_early + break_close_late)) leaveBreak(); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 56c246953e..95e7d13ee7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) { circle.ApproachCircle.Hide(); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index a05e4dea03..07ce009cf8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable hitCircle = null) { var h = hitObject.HitObject; - using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) (hitCircle ?? hitObject).Hide(); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index b5905d7015..8122ab563e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1; double moveDuration = hitObject.TimePreempt + 1; - using (drawable.BeginAbsoluteSequence(appearTime, true)) + using (drawable.BeginAbsoluteSequence(appearTime)) { drawable .MoveToOffset(appearOffset) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index a01cec4bb3..ff6ba6e121 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration, true)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration)) wiggle(); } @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Mods for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration, true)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration)) wiggle(); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs index 542f3eff0d..4ea0831627 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs @@ -130,18 +130,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Spinner spinner = drawableSpinner.HitObject; - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) { this.ScaleTo(initial_scale); this.RotateTo(0); - using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) + using (BeginDelayedSequence(spinner.TimePreempt / 2)) { // constant ambient rotation to give the spinner "spinning" character. this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration); } - using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset, true)) + using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset)) { switch (state) { @@ -157,17 +157,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default } } - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) { centre.ScaleTo(0); mainContainer.ScaleTo(0); - using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) + using (BeginDelayedSequence(spinner.TimePreempt / 2)) { centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint); mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint); - using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) + using (BeginDelayedSequence(spinner.TimePreempt / 2)) { centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint); mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint); @@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default } // transforms we have from completing the spinner will be rolled back, so reapply immediately. - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) updateComplete(state == ArmedState.Hit, 0); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index 8feeca56e8..1f988df947 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default public override void ApplyTransformsAt(double time, bool propagateChildren = false) { // For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either. - base.ApplyTransformsAt(time, false); + base.ApplyTransformsAt(time); } private bool tracking; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index ae8d6a61f8..1e170036e4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -100,17 +100,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy case DrawableSpinner d: Spinner spinner = d.HitObject; - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) this.FadeOut(); - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2)) this.FadeInFromZero(spinner.TimeFadeIn / 2); - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) { fixedMiddle.FadeColour(Color4.White); - using (BeginDelayedSequence(spinner.TimePreempt, true)) + using (BeginDelayedSequence(spinner.TimePreempt)) fixedMiddle.FadeColour(Color4.Red, spinner.Duration); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs index cbe721d21d..e3e8f3ce88 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs @@ -89,10 +89,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Spinner spinner = d.HitObject; - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) this.FadeOut(); - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2)) this.FadeInFromZero(spinner.TimeFadeIn / 2); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index b2311dcb91..93aba608e6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { double startTime = Math.Min(Time.Current, DrawableSpinner.HitStateUpdateTime - 400); - using (BeginAbsoluteSequence(startTime, true)) + using (BeginAbsoluteSequence(startTime)) { clear.FadeInFromZero(400, Easing.Out); @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } const double fade_out_duration = 50; - using (BeginAbsoluteSequence(DrawableSpinner.HitStateUpdateTime - fade_out_duration, true)) + using (BeginAbsoluteSequence(DrawableSpinner.HitStateUpdateTime - fade_out_duration)) clear.FadeOut(fade_out_duration); } else @@ -182,14 +182,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy double spinFadeOutLength = Math.Min(400, d.HitObject.Duration); - using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength, true)) + using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength)) spin.FadeOutFromOne(spinFadeOutLength); break; case DrawableSpinnerTick d: if (state == ArmedState.Hit) { - using (BeginAbsoluteSequence(d.HitStateUpdateTime, true)) + using (BeginAbsoluteSequence(d.HitStateUpdateTime)) spin.FadeOut(300); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 60f9521996..888f47d341 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { base.UpdateStartTimeStateTransforms(); - using (BeginDelayedSequence(-ring_appear_offset, true)) + using (BeginDelayedSequence(-ring_appear_offset)) targetRing.ScaleTo(target_ring_scale, 400, Easing.OutQuint); } diff --git a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs index 6c8133660f..9fba0f1668 100644 --- a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs +++ b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestSingleSpan() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null, default).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestRepeat() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null, default).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestNonEvenTicks() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null, default).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestLegacyLastTickOffset() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100, default).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray(); Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick)); Assert.That(events[2].Time, Is.EqualTo(900)); @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps const double velocity = 5; const double min_distance = velocity * 10; - var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0, default).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray(); Assert.Multiple(() => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs index 4ee48fd853..11bd701e19 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs @@ -114,11 +114,6 @@ namespace osu.Game.Tests.Visual.Gameplay { public bool ResultsCreated { get; private set; } - public FakeRankingPushPlayer() - : base(true, true) - { - } - protected override ResultsScreen CreateResults(ScoreInfo score) { var results = base.CreateResults(score); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index f29fbbf52b..02b1959dab 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Gameplay Children = new[] { new ExposedSkinnableDrawable("default", _ => new DefaultBox()), - new ExposedSkinnableDrawable("available", _ => new DefaultBox(), ConfineMode.ScaleToFit), + new ExposedSkinnableDrawable("available", _ => new DefaultBox()), new ExposedSkinnableDrawable("available", _ => new DefaultBox(), ConfineMode.NoScaling) } }, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 469f594fdc..bb577886cc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.Gameplay foreach (var legacyFrame in frames.Frames) { var frame = new TestReplayFrame(); - frame.FromLegacy(legacyFrame, null, null); + frame.FromLegacy(legacyFrame, null); replay.Frames.Add(frame); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b8db4067fb..072e32370d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // Send initial frames for both players. A few more for player 1. sendFrames(PLAYER_1_ID, 20); - sendFrames(PLAYER_2_ID, 10); + sendFrames(PLAYER_2_ID); checkPausedInstant(PLAYER_1_ID, false); checkPausedInstant(PLAYER_2_ID, false); @@ -194,7 +194,7 @@ namespace osu.Game.Tests.Visual.Multiplayer assertMuted(PLAYER_1_ID, true); assertMuted(PLAYER_2_ID, true); - sendFrames(PLAYER_1_ID, 10); + sendFrames(PLAYER_1_ID); sendFrames(PLAYER_2_ID, 20); checkPaused(PLAYER_1_ID, false); assertOneNotMuted(); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs index cd226662d7..4ce684d5af 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestInitialVisibility() { - AddStep("Create header with 0 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero, 0)); + AddStep("Create header with 0 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero)); AddAssert("Value is 0", () => header.Current.Value == 0); AddAssert("Counter is visible", () => header.ChildrenOfType().First().Alpha == 1); diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index e4ec45c00e..6e4c6784c8 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -172,7 +172,7 @@ namespace osu.Game.Tournament.Screens.Gameplay { chat?.Contract(); - using (BeginDelayedSequence(300, true)) + using (BeginDelayedSequence(300)) { scoreDisplay.FadeIn(100); SongBar.Expanded = true; diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 1c9cdc174a..e2a0c09a6b 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -97,7 +97,7 @@ namespace osu.Game.Graphics.Containers if (timingPoint == lastTimingPoint && beatIndex == lastBeat) return; - using (BeginDelayedSequence(-TimeSinceLastBeat, true)) + using (BeginDelayedSequence(-TimeSinceLastBeat)) OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? ChannelAmplitudes.Empty); lastBeat = beatIndex; diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index 894a21fcf3..32b788b5dc 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -113,7 +113,7 @@ namespace osu.Game.Graphics.UserInterface double delay = (current <= newValue ? Math.Max(i - current, 0) : Math.Max(current - 1 - i, 0)) * AnimationDelay; - using (star.BeginDelayedSequence(delay, true)) + using (star.BeginDelayedSequence(delay)) star.DisplayAt(getStarScale(i, newValue)); } } diff --git a/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs b/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs index 25e6b3f1af..20856c2768 100644 --- a/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs @@ -13,7 +13,7 @@ namespace osu.Game.Online.API.Requests private readonly RankingsSortCriteria sort; public GetSpotlightRankingsRequest(RulesetInfo ruleset, int spotlight, RankingsSortCriteria sort) - : base(ruleset, 1) + : base(ruleset) { this.spotlight = spotlight; this.sort = sort; diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 70e38e421d..4f8b27602b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -82,7 +82,7 @@ namespace osu.Game.Online.Leaderboards foreach (var s in scrollFlow.Children) { - using (s.BeginDelayedSequence(i++ * 50, true)) + using (s.BeginDelayedSequence(i++ * 50)) s.Show(); } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index e35d3d6461..7108a23e44 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -248,7 +248,7 @@ namespace osu.Game.Online.Leaderboards this.FadeIn(200); content.MoveToY(0, 800, Easing.OutQuint); - using (BeginDelayedSequence(100, true)) + using (BeginDelayedSequence(100)) { avatar.FadeIn(300, Easing.OutQuint); nameLabel.FadeIn(350, Easing.OutQuint); @@ -256,12 +256,12 @@ namespace osu.Game.Online.Leaderboards avatar.MoveToX(0, 300, Easing.OutQuint); nameLabel.MoveToX(0, 350, Easing.OutQuint); - using (BeginDelayedSequence(250, true)) + using (BeginDelayedSequence(250)) { scoreLabel.FadeIn(200); scoreRank.FadeIn(200); - using (BeginDelayedSequence(50, true)) + using (BeginDelayedSequence(50)) { var drawables = new Drawable[] { flagBadgeContainer, modsContainer }.Concat(statisticsLabels).ToArray(); for (int i = 0; i < drawables.Length; i++) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 0feae16b68..e15625a4b3 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -213,7 +213,7 @@ namespace osu.Game.Overlays innerSpin.Spin(20000, RotationDirection.Clockwise); outerSpin.Spin(40000, RotationDirection.Clockwise); - using (BeginDelayedSequence(200, true)) + using (BeginDelayedSequence(200)) { disc.FadeIn(initial_duration) .ScaleTo(1f, initial_duration * 2, Easing.OutElastic); @@ -221,7 +221,7 @@ namespace osu.Game.Overlays particleContainer.FadeIn(initial_duration); outerSpin.FadeTo(0.1f, initial_duration * 2); - using (BeginDelayedSequence(initial_duration + 200, true)) + using (BeginDelayedSequence(initial_duration + 200)) { backgroundStrip.FadeIn(step_duration); leftStrip.ResizeWidthTo(1f, step_duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index d0bd24496a..572ff0d1aa 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Mods backgroundIcon.Mod = newSelection; - using (BeginDelayedSequence(mod_switch_duration, true)) + using (BeginDelayedSequence(mod_switch_duration)) { foregroundIcon .RotateTo(-rotate_angle * direction) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e4aab978fc..6c47b92d29 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -128,7 +128,7 @@ namespace osu.Game.Overlays.Mods RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 90), - new Dimension(GridSizeMode.Distributed), + new Dimension(), new Dimension(GridSizeMode.AutoSize), }, Content = new[] diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index 94bfd62c32..56c54425bd 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.News public Action ShowFrontPage; - private readonly Bindable article = new Bindable(null); + private readonly Bindable article = new Bindable(); public NewsHeader() { diff --git a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs index e4c0fe3a5a..3cdf110090 100644 --- a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs +++ b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs @@ -57,7 +57,7 @@ namespace osu.Game.Overlays.Profile.Header.Components ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Distributed) + new Dimension() }, Content = new[] { diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 943897581e..e8a76c64ec 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Rankings.Tables private static TableColumn[] mainHeaders => new[] { new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 40)), // place - new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username (country name) + new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension()), // flag and username (country name) }; protected abstract TableColumn[] CreateAdditionalHeaders(); diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 8a57b4af91..0f22d35bb5 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Judgements LifetimeStart = Result.TimeAbsolute; - using (BeginAbsoluteSequence(Result.TimeAbsolute, true)) + using (BeginAbsoluteSequence(Result.TimeAbsolute)) { // not sure if this should remain going forward. JudgementBody.ResetAnimation(); diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs index 0d344b5269..872daadd46 100644 --- a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods }; [SettingSource("Direction", "The direction of rotation")] - public Bindable Direction { get; } = new Bindable(RotationDirection.Clockwise); + public Bindable Direction { get; } = new Bindable(); public override string Name => "Barrel Roll"; public override string Acronym => "BR"; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index a0717ec38e..c5db806918 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -404,13 +404,13 @@ namespace osu.Game.Rulesets.Objects.Drawables clearExistingStateTransforms(); - using (BeginAbsoluteSequence(transformTime, true)) + using (BeginAbsoluteSequence(transformTime)) UpdateInitialTransforms(); - using (BeginAbsoluteSequence(StateUpdateTime, true)) + using (BeginAbsoluteSequence(StateUpdateTime)) UpdateStartTimeStateTransforms(); - using (BeginAbsoluteSequence(HitStateUpdateTime, true)) + using (BeginAbsoluteSequence(HitStateUpdateTime)) UpdateHitStateTransforms(newState); state.Value = newState; diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 38290a6530..bdb0157746 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -297,7 +297,7 @@ namespace osu.Game.Screens.Menu Logger.Log($"{nameof(ButtonSystem)}'s state changed from {lastState} to {state}"); - using (buttonArea.BeginDelayedSequence(lastState == ButtonSystemState.Initial ? 150 : 0, true)) + using (buttonArea.BeginDelayedSequence(lastState == ButtonSystemState.Initial ? 150 : 0)) { buttonArea.ButtonSystemState = state; diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index a71b53157a..7f34e1e395 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -181,7 +181,7 @@ namespace osu.Game.Screens.Menu icon.Delay(500).FadeIn(500).ScaleTo(1, 500, Easing.OutQuint); - using (BeginDelayedSequence(3000, true)) + using (BeginDelayedSequence(3000)) { icon.FadeColour(iconColour, 200, Easing.OutQuint); icon.MoveToY(icon_y * 1.3f, 500, Easing.OutCirc) diff --git a/osu.Game/Screens/Menu/IntroSequence.cs b/osu.Game/Screens/Menu/IntroSequence.cs index d92d38da45..3a5cd6857a 100644 --- a/osu.Game/Screens/Menu/IntroSequence.cs +++ b/osu.Game/Screens/Menu/IntroSequence.cs @@ -189,7 +189,7 @@ namespace osu.Game.Screens.Menu double remainingTime() => length - TransformDelay; - using (BeginDelayedSequence(250, true)) + using (BeginDelayedSequence(250)) { welcomeText.FadeIn(700); welcomeText.TransformSpacingTo(new Vector2(20, 0), remainingTime(), Easing.Out); @@ -212,17 +212,17 @@ namespace osu.Game.Screens.Menu lineBottomLeft.MoveTo(new Vector2(-line_end_offset, line_end_offset), line_duration, Easing.OutQuint); lineBottomRight.MoveTo(new Vector2(line_end_offset, line_end_offset), line_duration, Easing.OutQuint); - using (BeginDelayedSequence(length * 0.56, true)) + using (BeginDelayedSequence(length * 0.56)) { bigRing.ResizeTo(logo_size, 500, Easing.InOutQuint); bigRing.Foreground.Delay(250).ResizeTo(1, 850, Easing.OutQuint); - using (BeginDelayedSequence(250, true)) + using (BeginDelayedSequence(250)) { backgroundFill.ResizeHeightTo(1, remainingTime(), Easing.InOutQuart); backgroundFill.RotateTo(-90, remainingTime(), Easing.InOutQuart); - using (BeginDelayedSequence(50, true)) + using (BeginDelayedSequence(50)) { foregroundFill.ResizeWidthTo(1, remainingTime(), Easing.InOutQuart); foregroundFill.RotateTo(-90, remainingTime(), Easing.InOutQuart); @@ -239,19 +239,19 @@ namespace osu.Game.Screens.Menu purpleCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); purpleCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); - using (BeginDelayedSequence(appear_delay, true)) + using (BeginDelayedSequence(appear_delay)) { yellowCircle.MoveToY(-circle_size / 2, remainingTime(), Easing.InOutQuart); yellowCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); yellowCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); - using (BeginDelayedSequence(appear_delay, true)) + using (BeginDelayedSequence(appear_delay)) { blueCircle.MoveToX(-circle_size / 2, remainingTime(), Easing.InOutQuart); blueCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); blueCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); - using (BeginDelayedSequence(appear_delay, true)) + using (BeginDelayedSequence(appear_delay)) { pinkCircle.MoveToX(circle_size / 2, remainingTime(), Easing.InOutQuart); pinkCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index abe6c62461..0ea83fe5e7 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -172,27 +172,27 @@ namespace osu.Game.Screens.Menu lazerLogo.Hide(); background.ApplyToBackground(b => b.Hide()); - using (BeginAbsoluteSequence(0, true)) + using (BeginAbsoluteSequence(0)) { - using (BeginDelayedSequence(text_1, true)) + using (BeginDelayedSequence(text_1)) welcomeText.FadeIn().OnComplete(t => t.Text = "wel"); - using (BeginDelayedSequence(text_2, true)) + using (BeginDelayedSequence(text_2)) welcomeText.FadeIn().OnComplete(t => t.Text = "welcome"); - using (BeginDelayedSequence(text_3, true)) + using (BeginDelayedSequence(text_3)) welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to"); - using (BeginDelayedSequence(text_4, true)) + using (BeginDelayedSequence(text_4)) { welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to osu!"); welcomeText.TransformTo(nameof(welcomeText.Spacing), new Vector2(50, 0), 5000); } - using (BeginDelayedSequence(text_glitch, true)) + using (BeginDelayedSequence(text_glitch)) triangles.FadeIn(); - using (BeginDelayedSequence(rulesets_1, true)) + using (BeginDelayedSequence(rulesets_1)) { rulesetsScale.ScaleTo(0.8f, 1000); rulesets.FadeIn().ScaleTo(1).TransformSpacingTo(new Vector2(200, 0)); @@ -200,18 +200,18 @@ namespace osu.Game.Screens.Menu triangles.FadeOut(); } - using (BeginDelayedSequence(rulesets_2, true)) + using (BeginDelayedSequence(rulesets_2)) { rulesets.ScaleTo(2).TransformSpacingTo(new Vector2(30, 0)); } - using (BeginDelayedSequence(rulesets_3, true)) + using (BeginDelayedSequence(rulesets_3)) { rulesets.ScaleTo(4).TransformSpacingTo(new Vector2(10, 0)); rulesetsScale.ScaleTo(1.3f, 1000); } - using (BeginDelayedSequence(logo_1, true)) + using (BeginDelayedSequence(logo_1)) { rulesets.FadeOut(); @@ -223,7 +223,7 @@ namespace osu.Game.Screens.Menu logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad); } - using (BeginDelayedSequence(logo_2, true)) + using (BeginDelayedSequence(logo_2)) { lazerLogo.FadeOut().OnComplete(_ => { diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 521e863683..f74043b045 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Menu { base.LoadComplete(); - using (BeginDelayedSequence(0, true)) + using (BeginDelayedSequence(0)) { scaleContainer.ScaleTo(0.9f).ScaleTo(1, delay_step_two).OnComplete(_ => Expire()); scaleContainer.FadeInFromZero(1800); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index fe9979b161..2e180f31fd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match RelativeSizeAxes = Axes.Both, RowDimensions = new[] { - new Dimension(GridSizeMode.Distributed), + new Dimension(), new Dimension(GridSizeMode.AutoSize), }, Content = new[] diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs index 5062a296a8..5eb2b545cb 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists RelativeSizeAxes = Axes.Both, RowDimensions = new[] { - new Dimension(GridSizeMode.Distributed), + new Dimension(), new Dimension(GridSizeMode.AutoSize), }, Content = new[] diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 36f825b8f6..1665ee83ae 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Play if (!b.HasEffect) continue; - using (BeginAbsoluteSequence(b.StartTime, true)) + using (BeginAbsoluteSequence(b.StartTime)) { fadeContainer.FadeIn(BREAK_FADE_DURATION); breakArrows.Show(BREAK_FADE_DURATION); @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Play remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration); - using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION, true)) + using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION)) { fadeContainer.FadeOut(BREAK_FADE_DURATION); breakArrows.Hide(BREAK_FADE_DURATION); diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index acff949353..edddc06746 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -170,9 +170,9 @@ namespace osu.Game.Screens.Play.HUD popOutCount.FadeTo(0.75f); popOutCount.MoveTo(Vector2.Zero); - popOutCount.ScaleTo(1, pop_out_duration, pop_out_easing); - popOutCount.FadeOut(pop_out_duration, pop_out_easing); - popOutCount.MoveTo(displayedCountSpriteText.Position, pop_out_duration, pop_out_easing); + popOutCount.ScaleTo(1, pop_out_duration); + popOutCount.FadeOut(pop_out_duration); + popOutCount.MoveTo(displayedCountSpriteText.Position, pop_out_duration); } private void transformNoPopOut(int newValue) @@ -186,7 +186,7 @@ namespace osu.Game.Screens.Play.HUD { ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); displayedCountSpriteText.ScaleTo(1.1f); - displayedCountSpriteText.ScaleTo(1, pop_out_duration, pop_out_easing); + displayedCountSpriteText.ScaleTo(1, pop_out_duration); } private void scheduledPopOutSmall(uint id) @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Play.HUD } private void transformRoll(int currentValue, int newValue) => - this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue), Easing.None); + this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue)); private string formatCount(int count) => $@"{count}x"; diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 0a10eee644..4d3f7a4184 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -256,7 +256,7 @@ namespace osu.Game.Screens.Ranking.Expanded // Score counter value setting must be scheduled so it isn't transferred instantaneously ScheduleAfterChildren(() => { - using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY, true)) + using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY)) { scoreCounter.FadeIn(); scoreCounter.Current = scoreManager.GetBindableTotalScore(score); @@ -265,7 +265,7 @@ namespace osu.Game.Screens.Ranking.Expanded foreach (var stat in statisticDisplays) { - using (BeginDelayedSequence(delay, true)) + using (BeginDelayedSequence(delay)) stat.Appear(); delay += 200; diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs index e13138c5a0..b92c244174 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics if (isPerfect) { - using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DURATION / 2, true)) + using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DURATION / 2)) perfectText.FadeIn(50); } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index f66a998db6..6ddecf8297 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -234,7 +234,7 @@ namespace osu.Game.Screens.Ranking bool topLayerExpanded = topLayerContainer.Y < 0; // If the top layer was already expanded, then we don't need to wait for the resize and can instead transform immediately. This looks better when changing the panel state. - using (BeginDelayedSequence(topLayerExpanded ? 0 : RESIZE_DURATION + TOP_LAYER_EXPAND_DELAY, true)) + using (BeginDelayedSequence(topLayerExpanded ? 0 : RESIZE_DURATION + TOP_LAYER_EXPAND_DELAY)) { topLayerContainer.FadeIn(); diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index cf0c183766..8b38b67f5c 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -191,7 +191,7 @@ namespace osu.Game.Screens boxContainer.ScaleTo(0.2f); boxContainer.RotateTo(-20); - using (BeginDelayedSequence(300, true)) + using (BeginDelayedSequence(300)) { boxContainer.ScaleTo(1, transition_time, Easing.OutElastic); boxContainer.RotateTo(0, transition_time / 2, Easing.OutQuint); diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index ca25efaa01..e76f251ce5 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -72,7 +72,7 @@ namespace osu.Game.Skinning if (particles != null) { // start the particles already some way into their animation to break cluster away from centre. - using (particles.BeginDelayedSequence(-100, true)) + using (particles.BeginDelayedSequence(-100)) particles.Restart(); } diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index d7b02ef797..a393802309 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual private readonly WorkingBeatmap testBeatmap; public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap, WorkingBeatmap testBeatmap) - : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap, false) + : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) { this.testBeatmap = testBeatmap; } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 98aad821ce..57e400a77e 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -176,7 +176,7 @@ namespace osu.Game.Tests.Visual protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset); protected WorkingBeatmap CreateWorkingBeatmap(RulesetInfo ruleset) => - CreateWorkingBeatmap(CreateBeatmap(ruleset), null); + CreateWorkingBeatmap(CreateBeatmap(ruleset)); protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, Clock, Audio); From f5ddd2a53bd0db4d49e6352a386ae62bf92d6d31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 01:15:30 +0900 Subject: [PATCH 1319/1621] Fix critical failure causing scores to not update on the leaderboard --- .../Play/HUD/MultiplayerGameplayLeaderboard.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 0c13e87f9f..4783ce2f40 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -71,11 +71,19 @@ namespace osu.Game.Screens.Play.HUD UserScores[user.Id] = trackedUser; } - - prepareDataStreams(); })); } + protected override void LoadComplete() + { + base.LoadComplete(); + + // this is *required* to be here due to the spectator leaderboard not correctly populating clocks if done later. + // note that running this here is probably not 100% correct (if a user quits before user population occurs for instance, + // an incorrect state will be reached). + prepareDataStreams(); + } + private void prepareDataStreams() { // BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually.. From d495196b66d7d3b63cd3909f08778b21f1e071bf Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 5 Jul 2021 19:22:55 +0200 Subject: [PATCH 1320/1621] Share item cycling logic with GameplayMenuOverlay --- .../Gameplay/TestSceneGameplayMenuOverlay.cs | 34 ++++----- .../SelectionCycleFillFlowContainer.cs | 55 ++++++++++++++ .../Graphics/UserInterface/DialogButton.cs | 36 +++++---- .../Graphics/UserInterface/ISelectable.cs | 10 +++ osu.Game/Overlays/Volume/VolumeMeter.cs | 45 +++++++----- osu.Game/Overlays/VolumeOverlay.cs | 70 +++++++----------- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 73 +++++-------------- 7 files changed, 174 insertions(+), 149 deletions(-) create mode 100644 osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs create mode 100644 osu.Game/Graphics/UserInterface/ISelectable.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index d69ac665cc..0aafbda951 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Gameplay showOverlay(); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value); + AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected); } /// @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Gameplay showOverlay(); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => getButton(0).Selected.Value); + AddAssert("First button selected", () => getButton(0).Selected); } /// @@ -111,11 +111,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Show overlay", () => failOverlay.Show()); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); } /// @@ -127,11 +127,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Show overlay", () => failOverlay.Show()); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); } /// @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First())); AddStep("Hide overlay", () => failOverlay.Hide()); - AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected.Value)); + AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected)); } /// @@ -162,11 +162,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hide overlay", () => pauseOverlay.Hide()); showOverlay(); - AddAssert("First button not selected", () => !getButton(0).Selected.Value); + AddAssert("First button not selected", () => !getButton(0).Selected); AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1))); - AddAssert("First button selected", () => getButton(0).Selected.Value); + AddAssert("First button selected", () => getButton(0).Selected); } /// @@ -179,8 +179,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Down arrow", () => InputManager.Key(Key.Down)); AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); - AddAssert("First button not selected", () => !getButton(0).Selected.Value); - AddAssert("Second button selected", () => getButton(1).Selected.Value); + AddAssert("First button not selected", () => !getButton(0).Selected); + AddAssert("Second button selected", () => getButton(1).Selected); } /// @@ -196,8 +196,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Second button not selected", () => !getButton(1).Selected.Value); - AddAssert("First button selected", () => getButton(0).Selected.Value); + AddAssert("Second button not selected", () => !getButton(1).Selected); + AddAssert("First button selected", () => getButton(0).Selected); } /// @@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero)); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition + AddAssert("First button selected", () => getButton(0).Selected); // Initial state condition } /// @@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.Gameplay showOverlay(); AddAssert("No button selected", - () => pauseOverlay.Buttons.All(button => !button.Selected.Value)); + () => pauseOverlay.Buttons.All(button => !button.Selected)); } private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show()); diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs new file mode 100644 index 0000000000..0e7b2bcc05 --- /dev/null +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A FillFlowContainer that provides functionality to cycle selection between children + /// The selection wraps around when overflowing past the first or last child. + /// + public class SelectionCycleFillFlowContainer : FillFlowContainer where T : Drawable, ISelectable + { + private int selectedIndex = -1; + + private void setSelected(int value) + { + if (selectedIndex == value) + return; + + // Deselect the previously-selected button + if (selectedIndex != -1) + this[selectedIndex].Selected = false; + + selectedIndex = value; + + // Select the newly-selected button + if (selectedIndex != -1) + this[selectedIndex].Selected = true; + } + + public void SelectNext() + { + if (selectedIndex == -1 || selectedIndex == Count - 1) + setSelected(0); + else + setSelected(selectedIndex + 1); + } + + public void SelectPrevious() + { + if (selectedIndex == -1 || selectedIndex == 0) + setSelected(Count - 1); + else + setSelected(selectedIndex - 1); + } + + public void Deselect() => setSelected(-1); + public void Select(T item) => setSelected(IndexOf(item)); + + public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex] : null; + } +} diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 1047aa4255..718a5171c2 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -2,24 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osuTK; -using osuTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Sprites; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics.Effects; -using osu.Game.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { - public class DialogButton : OsuClickableContainer + public class DialogButton : OsuClickableContainer, ISelectable { private const float idle_width = 0.8f; private const float hover_width = 0.9f; @@ -27,7 +27,13 @@ namespace osu.Game.Graphics.UserInterface private const float hover_duration = 500; private const float click_duration = 200; - public readonly BindableBool Selected = new BindableBool(); + public readonly BindableBool SelectedBindable = new BindableBool(); + + public bool Selected + { + get => SelectedBindable.Value; + set => SelectedBindable.Value = value; + } private readonly Container backgroundContainer; private readonly Container colourContainer; @@ -153,7 +159,7 @@ namespace osu.Game.Graphics.UserInterface updateGlow(); - Selected.ValueChanged += selectionChanged; + SelectedBindable.ValueChanged += selectionChanged; } private Color4 buttonColour; @@ -221,7 +227,7 @@ namespace osu.Game.Graphics.UserInterface .OnComplete(_ => { clickAnimating = false; - Selected.TriggerChange(); + SelectedBindable.TriggerChange(); }); return base.OnClick(e); @@ -235,7 +241,7 @@ namespace osu.Game.Graphics.UserInterface protected override void OnMouseUp(MouseUpEvent e) { - if (Selected.Value) + if (Selected) colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In); base.OnMouseUp(e); } @@ -243,7 +249,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { base.OnHover(e); - Selected.Value = true; + Selected = true; return true; } @@ -251,7 +257,7 @@ namespace osu.Game.Graphics.UserInterface protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - Selected.Value = false; + Selected = false; } private void selectionChanged(ValueChangedEvent args) diff --git a/osu.Game/Graphics/UserInterface/ISelectable.cs b/osu.Game/Graphics/UserInterface/ISelectable.cs new file mode 100644 index 0000000000..49c3d725c8 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ISelectable.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 interface ISelectable + { + bool Selected { get; set; } + } +} diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 9210001a3a..3e9849a077 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -19,13 +19,14 @@ using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Volume { - public class VolumeMeter : Container, IKeyBindingHandler + public class VolumeMeter : Container, IKeyBindingHandler, ISelectable { private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -43,7 +44,13 @@ namespace osu.Game.Overlays.Volume private Sample sample; private double sampleLastPlaybackTime; - public Action RequestFocus; + public BindableBool SelectedBindable = new BindableBool(); + + public bool Selected + { + get => SelectedBindable.Value; + set => SelectedBindable.Value = value; + } public VolumeMeter(string name, float circleSize, Color4 meterColour) { @@ -212,6 +219,8 @@ namespace osu.Game.Overlays.Volume Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true); bgProgress.Current.Value = 0.75f; + + SelectedBindable.ValueChanged += selectionChanged; } private int? displayVolumeInt; @@ -330,21 +339,9 @@ namespace osu.Game.Overlays.Volume private const float transition_length = 500; - public void Focus() - { - this.ScaleTo(1.04f, transition_length, Easing.OutExpo); - focusGlowContainer.FadeIn(transition_length, Easing.OutExpo); - } - - public void Unfocus() - { - this.ScaleTo(1f, transition_length, Easing.OutExpo); - focusGlowContainer.FadeOut(transition_length, Easing.OutExpo); - } - protected override bool OnHover(HoverEvent e) { - RequestFocus?.Invoke(this); + Selected = true; return false; } @@ -352,6 +349,20 @@ namespace osu.Game.Overlays.Volume { } + private void selectionChanged(ValueChangedEvent selected) + { + if (selected.NewValue) + { + this.ScaleTo(1.04f, transition_length, Easing.OutExpo); + focusGlowContainer.FadeIn(transition_length, Easing.OutExpo); + } + else + { + this.ScaleTo(1f, transition_length, Easing.OutExpo); + focusGlowContainer.FadeOut(transition_length, Easing.OutExpo); + } + } + public bool OnPressed(GlobalAction action) { if (!IsHovered) @@ -360,12 +371,12 @@ namespace osu.Game.Overlays.Volume switch (action) { case GlobalAction.SelectPrevious: - RequestFocus?.Invoke(this); + Selected = true; adjust(1, false); return true; case GlobalAction.SelectNext: - RequestFocus?.Invoke(this); + Selected = true; adjust(-1, false); return true; } diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index dca39774f2..0c56e48cd4 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Overlays.Volume; using osuTK; @@ -32,7 +33,7 @@ namespace osu.Game.Overlays public Bindable IsMuted { get; } = new Bindable(); - private FillFlowContainer volumeMeters; + private SelectionCycleFillFlowContainer volumeMeters; [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) @@ -55,7 +56,7 @@ namespace osu.Game.Overlays Margin = new MarginPadding(10), Current = { BindTarget = IsMuted } }, - volumeMeters = new FillFlowContainer + volumeMeters = new SelectionCycleFillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, @@ -65,18 +66,9 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Left = offset }, Children = new[] { - volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker) - { - RequestFocus = v => focusedMeter.Value = v - }, - volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker) - { - RequestFocus = v => focusedMeter.Value = v - }, - volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker) - { - RequestFocus = v => focusedMeter.Value = v - }, + volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker), + volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker), + volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker), } } }); @@ -92,23 +84,18 @@ namespace osu.Game.Overlays else audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); }); - - focusedMeter.BindValueChanged(meter => - { - meter.OldValue?.Unfocus(); - meter.NewValue?.Focus(); - }); } - private readonly Bindable focusedMeter = new Bindable(); - protected override void LoadComplete() { base.LoadComplete(); - volumeMeterMaster.Bindable.ValueChanged += _ => Show(); - volumeMeterEffect.Bindable.ValueChanged += _ => Show(); - volumeMeterMusic.Bindable.ValueChanged += _ => Show(); + foreach (var volumeMeter in volumeMeters) + { + volumeMeter.Bindable.ValueChanged += _ => Show(); + volumeMeter.SelectedBindable.ValueChanged += selected => volumeMeterSelectionChanged(volumeMeter, selected.NewValue); + } + muteButton.Current.ValueChanged += _ => Show(); } @@ -122,28 +109,26 @@ namespace osu.Game.Overlays if (State.Value == Visibility.Hidden) Show(); else - focusedMeter.Value.Decrease(amount, isPrecise); + volumeMeters.Selected?.Decrease(amount, isPrecise); return true; case GlobalAction.IncreaseVolume: if (State.Value == Visibility.Hidden) Show(); else - focusedMeter.Value.Increase(amount, isPrecise); + volumeMeters.Selected?.Increase(amount, isPrecise); return true; case GlobalAction.NextVolumeMeter: - if (State.Value == Visibility.Hidden) - Show(); - else - focusShift(1); + if (State.Value == Visibility.Visible) + volumeMeters.SelectNext(); + Show(); return true; case GlobalAction.PreviousVolumeMeter: - if (State.Value == Visibility.Hidden) - Show(); - else - focusShift(-1); + if (State.Value == Visibility.Visible) + volumeMeters.SelectPrevious(); + Show(); return true; case GlobalAction.ToggleMute: @@ -155,15 +140,12 @@ namespace osu.Game.Overlays return false; } - private void focusShift(int direction = 1) + private void volumeMeterSelectionChanged(VolumeMeter meter, bool isSelected) { - Show(); - - var newIndex = volumeMeters.IndexOf(focusedMeter.Value) + direction; - if (newIndex < 0) - newIndex += volumeMeters.Count; - - focusedMeter.Value = volumeMeters.Children[newIndex % volumeMeters.Count]; + if (!isSelected) + volumeMeters.Deselect(); + else + volumeMeters.Select(meter); } private ScheduledDelegate popOutDelegate; @@ -172,7 +154,7 @@ namespace osu.Game.Overlays { // Focus on the master meter as a default if previously hidden if (State.Value == Visibility.Hidden) - focusedMeter.Value = volumeMeterMaster; + volumeMeters.Select(volumeMeterMaster); if (State.Value == Visibility.Visible) schedulePopOut(); diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 4a28da0dde..876f33d814 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -2,23 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; +using Humanizer; +using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osuTK; -using osuTK.Graphics; -using osu.Game.Graphics; -using osu.Framework.Allocation; -using osu.Game.Graphics.UserInterface; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; -using Humanizer; -using osu.Framework.Graphics.Effects; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Play { @@ -41,18 +42,18 @@ namespace osu.Game.Screens.Play /// /// Action that is invoked when is triggered. /// - protected virtual Action BackAction => () => InternalButtons.Children.LastOrDefault()?.Click(); + protected virtual Action BackAction => () => InternalButtons.Selected?.Click(); /// /// Action that is invoked when is triggered. /// - protected virtual Action SelectAction => () => InternalButtons.Children.FirstOrDefault(f => f.Selected.Value)?.Click(); + protected virtual Action SelectAction => () => InternalButtons.Selected?.Click(); public abstract string Header { get; } public abstract string Description { get; } - protected ButtonContainer InternalButtons; + protected SelectionCycleFillFlowContainer InternalButtons; public IReadOnlyList Buttons => InternalButtons; private FillFlowContainer retryCounterContainer; @@ -116,7 +117,7 @@ namespace osu.Game.Screens.Play } } }, - InternalButtons = new ButtonContainer + InternalButtons = new SelectionCycleFillFlowContainer { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, @@ -183,7 +184,7 @@ namespace osu.Game.Screens.Play } }; - button.Selected.ValueChanged += selected => buttonSelectionChanged(button, selected.NewValue); + button.SelectedBindable.ValueChanged += selected => buttonSelectionChanged(button, selected.NewValue); InternalButtons.Add(button); } @@ -255,46 +256,6 @@ namespace osu.Game.Screens.Play }; } - protected class ButtonContainer : FillFlowContainer - { - private int selectedIndex = -1; - - private void setSelected(int value) - { - if (selectedIndex == value) - return; - - // Deselect the previously-selected button - if (selectedIndex != -1) - this[selectedIndex].Selected.Value = false; - - selectedIndex = value; - - // Select the newly-selected button - if (selectedIndex != -1) - this[selectedIndex].Selected.Value = true; - } - - public void SelectNext() - { - if (selectedIndex == -1 || selectedIndex == Count - 1) - setSelected(0); - else - setSelected(selectedIndex + 1); - } - - public void SelectPrevious() - { - if (selectedIndex == -1 || selectedIndex == 0) - setSelected(Count - 1); - else - setSelected(selectedIndex - 1); - } - - public void Deselect() => setSelected(-1); - public void Select(DialogButton button) => setSelected(IndexOf(button)); - } - private class Button : DialogButton { // required to ensure keyboard navigation always starts from an extremity (unless the cursor is moved) @@ -302,7 +263,7 @@ namespace osu.Game.Screens.Play protected override bool OnMouseMove(MouseMoveEvent e) { - Selected.Value = true; + Selected = true; return base.OnMouseMove(e); } } From 8e102280b7e289cb7d564b4ae7b8ba8fddde5cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Jul 2021 20:21:44 +0200 Subject: [PATCH 1321/1621] Revert & disable case with two contradicting inspections Leaving the `false` default value without the suppression triggers `RedundantArgumentDefaultValue`, while removing it triggers `BaseMethodCallWithDefaultParameter`. Disable the former, because a single redundant parameter is less bad than silent breakage if the default value of `propagateChildren` in the base method ever changes. --- osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index 1f988df947..8943a91076 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -86,7 +86,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default public override void ApplyTransformsAt(double time, bool propagateChildren = false) { // For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either. - base.ApplyTransformsAt(time); + // ReSharper disable once RedundantArgumentDefaultValue - removing the "redundant" default value triggers BaseMethodCallWithDefaultParameter + base.ApplyTransformsAt(time, false); } private bool tracking; From 686dd2b5ce552a02628844f58a34ecbdddf0d27a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Jul 2021 20:35:14 +0200 Subject: [PATCH 1322/1621] Remove unused constant --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index edddc06746..5c5b66d496 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -23,8 +23,6 @@ namespace osu.Game.Screens.Play.HUD private const double pop_out_duration = 150; - private const Easing pop_out_easing = Easing.None; - private const double fade_out_duration = 100; /// From af02a1efcbf6841b55b9925d80a8058840ae481e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Jul 2021 21:02:38 +0200 Subject: [PATCH 1323/1621] Bump redundant argument default value inspection severity to warning --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index d2c5b1223c..7284ca1a9a 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -130,7 +130,7 @@ HINT WARNING WARNING - HINT + WARNING WARNING WARNING WARNING From 7f2baef998947dd37accb18c2f94b6db5d66f181 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 14:24:17 +0900 Subject: [PATCH 1324/1621] Remove all `.resx` files from osu project --- osu.Game/Localisation/ButtonSystem.ja.resx | 38 ---------- osu.Game/Localisation/ButtonSystem.resx | 88 ---------------------- osu.Game/Localisation/Chat.resx | 67 ---------------- osu.Game/Localisation/Common.resx | 64 ---------------- osu.Game/Localisation/Notifications.resx | 67 ---------------- osu.Game/Localisation/NowPlaying.resx | 67 ---------------- osu.Game/Localisation/Settings.resx | 67 ---------------- 7 files changed, 458 deletions(-) delete mode 100644 osu.Game/Localisation/ButtonSystem.ja.resx delete mode 100644 osu.Game/Localisation/ButtonSystem.resx delete mode 100644 osu.Game/Localisation/Chat.resx delete mode 100644 osu.Game/Localisation/Common.resx delete mode 100644 osu.Game/Localisation/Notifications.resx delete mode 100644 osu.Game/Localisation/NowPlaying.resx delete mode 100644 osu.Game/Localisation/Settings.resx diff --git a/osu.Game/Localisation/ButtonSystem.ja.resx b/osu.Game/Localisation/ButtonSystem.ja.resx deleted file mode 100644 index 02f3e7ce2f..0000000000 --- a/osu.Game/Localisation/ButtonSystem.ja.resx +++ /dev/null @@ -1,38 +0,0 @@ - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - ソロ - - - プレイリスト - - - 遊ぶ - - - マルチ - - - エディット - - - ブラウズ - - - 閉じる - - - 設定 - - diff --git a/osu.Game/Localisation/ButtonSystem.resx b/osu.Game/Localisation/ButtonSystem.resx deleted file mode 100644 index d72ffff8be..0000000000 --- a/osu.Game/Localisation/ButtonSystem.resx +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - solo - - - multi - - - playlists - - - play - - - edit - - - browse - - - settings - - - back - - - exit - - \ No newline at end of file diff --git a/osu.Game/Localisation/Chat.resx b/osu.Game/Localisation/Chat.resx deleted file mode 100644 index 055e351463..0000000000 --- a/osu.Game/Localisation/Chat.resx +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - chat - - - join the real-time discussion - - \ No newline at end of file diff --git a/osu.Game/Localisation/Common.resx b/osu.Game/Localisation/Common.resx deleted file mode 100644 index 59de16a037..0000000000 --- a/osu.Game/Localisation/Common.resx +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Cancel - - \ No newline at end of file diff --git a/osu.Game/Localisation/Notifications.resx b/osu.Game/Localisation/Notifications.resx deleted file mode 100644 index 08db240ba2..0000000000 --- a/osu.Game/Localisation/Notifications.resx +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - notifications - - - waiting for 'ya - - \ No newline at end of file diff --git a/osu.Game/Localisation/NowPlaying.resx b/osu.Game/Localisation/NowPlaying.resx deleted file mode 100644 index 40fda3e25b..0000000000 --- a/osu.Game/Localisation/NowPlaying.resx +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - now playing - - - manage the currently playing track - - \ No newline at end of file diff --git a/osu.Game/Localisation/Settings.resx b/osu.Game/Localisation/Settings.resx deleted file mode 100644 index 85c224cedf..0000000000 --- a/osu.Game/Localisation/Settings.resx +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - settings - - - change the way osu! behaves - - \ No newline at end of file From 0ecda98b393c46991893db8e4d89f7dcec976bed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 14:28:52 +0900 Subject: [PATCH 1325/1621] Update prefixes to match new resx locations --- osu.Game/Localisation/ButtonSystemStrings.cs | 4 ++-- osu.Game/Localisation/ChatStrings.cs | 2 +- osu.Game/Localisation/CommonStrings.cs | 2 +- osu.Game/Localisation/NotificationsStrings.cs | 2 +- osu.Game/Localisation/NowPlayingStrings.cs | 2 +- osu.Game/Localisation/SettingsStrings.cs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Localisation/ButtonSystemStrings.cs b/osu.Game/Localisation/ButtonSystemStrings.cs index 8083f80782..ba4abf63a6 100644 --- a/osu.Game/Localisation/ButtonSystemStrings.cs +++ b/osu.Game/Localisation/ButtonSystemStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class ButtonSystemStrings { - private const string prefix = @"osu.Game.Localisation.ButtonSystem"; + private const string prefix = @"osu.Game.Resources.Localisation.ButtonSystem"; /// /// "solo" @@ -56,4 +56,4 @@ namespace osu.Game.Localisation private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs index 636351470b..7bd284a94e 100644 --- a/osu.Game/Localisation/ChatStrings.cs +++ b/osu.Game/Localisation/ChatStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class ChatStrings { - private const string prefix = @"osu.Game.Localisation.Chat"; + private const string prefix = @"osu.Game.Resources.Localisation.Chat"; /// /// "chat" diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index ced0d80955..50e01f06fc 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class CommonStrings { - private const string prefix = @"osu.Game.Localisation.Common"; + private const string prefix = @"osu.Game.Resources.Localisation.Common"; /// /// "Cancel" diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index ba28ef5560..382e0d81f4 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class NotificationsStrings { - private const string prefix = @"osu.Game.Localisation.Notifications"; + private const string prefix = @"osu.Game.Resources.Localisation.Notifications"; /// /// "notifications" diff --git a/osu.Game/Localisation/NowPlayingStrings.cs b/osu.Game/Localisation/NowPlayingStrings.cs index 47646b0f68..f334637338 100644 --- a/osu.Game/Localisation/NowPlayingStrings.cs +++ b/osu.Game/Localisation/NowPlayingStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class NowPlayingStrings { - private const string prefix = @"osu.Game.Localisation.NowPlaying"; + private const string prefix = @"osu.Game.Resources.Localisation.NowPlaying"; /// /// "now playing" diff --git a/osu.Game/Localisation/SettingsStrings.cs b/osu.Game/Localisation/SettingsStrings.cs index f4b417fa28..aa2e2740eb 100644 --- a/osu.Game/Localisation/SettingsStrings.cs +++ b/osu.Game/Localisation/SettingsStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class SettingsStrings { - private const string prefix = @"osu.Game.Localisation.Settings"; + private const string prefix = @"osu.Game.Resources.Localisation.Settings"; /// /// "settings" From 1a6b8b2c7353a5d3a988db49db138900ac271761 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 14:53:31 +0900 Subject: [PATCH 1326/1621] Populate `UserScores` as early as possible to avoid weird ordering requirements --- .../HUD/MultiplayerGameplayLeaderboard.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 4783ce2f40..a10c16fcd5 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -53,6 +53,13 @@ namespace osu.Game.Screens.Play.HUD { scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); + foreach (var userId in playingUsers) + { + var trackedUser = CreateUserData(userId, scoreProcessor); + trackedUser.ScoringMode.BindTo(scoringMode); + UserScores[userId] = trackedUser; + } + userLookupCache.GetUsersAsync(playingUsers.ToArray()).ContinueWith(users => Schedule(() => { foreach (var user in users.Result) @@ -60,16 +67,13 @@ namespace osu.Game.Screens.Play.HUD if (user == null) continue; - var trackedUser = CreateUserData(user.Id, scoreProcessor); - trackedUser.ScoringMode.BindTo(scoringMode); + var trackedUser = UserScores[user.Id]; var leaderboardScore = AddPlayer(user, user.Id == api.LocalUser.Value.Id); leaderboardScore.Accuracy.BindTo(trackedUser.Accuracy); leaderboardScore.TotalScore.BindTo(trackedUser.Score); leaderboardScore.Combo.BindTo(trackedUser.CurrentCombo); leaderboardScore.HasQuit.BindTo(trackedUser.UserQuit); - - UserScores[user.Id] = trackedUser; } })); } @@ -78,14 +82,6 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - // this is *required* to be here due to the spectator leaderboard not correctly populating clocks if done later. - // note that running this here is probably not 100% correct (if a user quits before user population occurs for instance, - // an incorrect state will be reached). - prepareDataStreams(); - } - - private void prepareDataStreams() - { // BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually.. foreach (int userId in playingUsers) { @@ -95,6 +91,8 @@ namespace osu.Game.Screens.Play.HUD usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId })); } + // bind here is to support players leaving the match. + // new players are not supported. playingUsers.BindTo(multiplayerClient.CurrentMatchPlayingUserIds); playingUsers.BindCollectionChanged(usersChanged); From 402b527903e92c0332d86f52b6eb5835682ad0bc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Jul 2021 14:54:56 +0900 Subject: [PATCH 1327/1621] Add .editorconfig for localisation analyser --- osu.Game/.editorconfig | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 osu.Game/.editorconfig diff --git a/osu.Game/.editorconfig b/osu.Game/.editorconfig new file mode 100644 index 0000000000..46a3dafd04 --- /dev/null +++ b/osu.Game/.editorconfig @@ -0,0 +1,2 @@ +[*.cs] +dotnet_diagnostic.OLOC001.prefix_namespace = osu.Game.Resources.Localisation \ No newline at end of file From 0658cfb986b4f8a06fac444075d7fa004be19cf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 14:56:00 +0900 Subject: [PATCH 1328/1621] Throw exceptions rather than silently failing if attempting to add a clock for a non-tracked user --- .../Multiplayer/Spectate/MultiSpectatorLeaderboard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs index ab3ead68b5..55c4270c70 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public void AddClock(int userId, IClock clock) { if (!UserScores.TryGetValue(userId, out var data)) - return; + throw new ArgumentException(@"Provided user is not tracked by this leaderboard", nameof(userId)); ((SpectatingTrackedUserData)data).Clock = clock; } @@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public void RemoveClock(int userId) { if (!UserScores.TryGetValue(userId, out var data)) - return; + throw new ArgumentException(@"Provided user is not tracked by this leaderboard", nameof(userId)); ((SpectatingTrackedUserData)data).Clock = null; } From 1beb85a26f699345bad516da6ba35054489d3399 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Jul 2021 14:56:43 +0900 Subject: [PATCH 1329/1621] Bump localisation analyser packages --- .config/dotnet-tools.json | 2 +- osu.Game/osu.Game.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index b3f7c67c51..97fcb52ab1 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -27,7 +27,7 @@ ] }, "ppy.localisationanalyser.tools": { - "version": "2021.608.0", + "version": "2021.705.0", "commands": [ "localisation" ] diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 357aa89329..626be76a84 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 7f7cf0b927124ede7673b8e28b2cfc6628ed0386 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 15:08:00 +0900 Subject: [PATCH 1330/1621] Fix potential failure during cleanup of files in migration tests --- .../NonVisual/CustomTourneyDirectoryTest.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 61f8511e3c..d2369056e1 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestDefaultDirectory() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestDefaultDirectory))) { try { @@ -139,8 +139,13 @@ namespace osu.Game.Tournament.Tests.NonVisual } finally { - host.Storage.Delete("tournament.ini"); - host.Storage.DeleteDirectory("tournaments"); + try + { + host.Storage.Delete("tournament.ini"); + host.Storage.DeleteDirectory("tournaments"); + } + catch { } + host.Exit(); } } From ae1b1cbec9333887806a2085c833747a628a5710 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 17:33:32 +0900 Subject: [PATCH 1331/1621] Allow serialization of catch hit objects --- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 15 +++++++++++++-- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 5 +++++ .../Objects/PalpableCatchHitObject.cs | 2 ++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index ae45182960..cdd8bfbe50 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.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 Newtonsoft.Json; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -20,6 +21,11 @@ namespace osu.Game.Rulesets.Catch.Objects /// /// The horizontal position of the hit object between 0 and . /// + /// + /// Only setter is exposed. + /// Use or to get the horizontal position. + /// + [JsonIgnore] public float X { set => OriginalXBindable.Value = value; @@ -34,6 +40,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// public float XOffset { + get => XOffsetBindable.Value; set => XOffsetBindable.Value = value; } @@ -44,7 +51,11 @@ namespace osu.Game.Rulesets.Catch.Objects /// This value is the original value specified in the beatmap, not affected by the beatmap processing. /// Use for a gameplay. /// - public float OriginalX => OriginalXBindable.Value; + public float OriginalX + { + get => OriginalXBindable.Value; + set => OriginalXBindable.Value = value; + } /// /// The effective horizontal position of the hit object between 0 and . @@ -55,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// public float EffectiveX => OriginalXBindable.Value + XOffsetBindable.Value; - public double TimePreempt = 1000; + public double TimePreempt { get; set; } = 1000; public readonly Bindable IndexInBeatmapBindable = new Bindable(); diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 35fd58826e..3088d024d1 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using Newtonsoft.Json; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -25,7 +26,10 @@ namespace osu.Game.Rulesets.Catch.Objects public int RepeatCount { get; set; } + [JsonIgnore] public double Velocity { get; private set; } + + [JsonIgnore] public double TickDistance { get; private set; } /// @@ -113,6 +117,7 @@ namespace osu.Game.Rulesets.Catch.Objects public float EndX => OriginalX + this.CurvePositionAt(1).X; + [JsonIgnore] public double Duration { get => this.SpanCount() * Path.Distance / Velocity; diff --git a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs index 0cd3af01df..aa7cabf38b 100644 --- a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Types; using osuTK.Graphics; @@ -33,6 +34,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// /// The target fruit if we are to initiate a hyperdash. /// + [JsonIgnore] public CatchHitObject HyperDashTarget { get => hyperDashTarget; From 175d666906bd742d7a47df3081c5dd1c31ef1b0a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 22 Jun 2021 17:35:30 +0900 Subject: [PATCH 1332/1621] Use getters of `OriginalX` and `XOffset` --- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 4 ++-- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index c1a491d1ce..d35d74d93d 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.Edit if (hitObject is BananaShower) return; // TODO: confine in bounds - hitObject.OriginalXBindable.Value += deltaX; + hitObject.OriginalX += deltaX; // Move the nested hit objects to give an instant result before nested objects are recreated. foreach (var nested in hitObject.NestedHitObjects.OfType()) - nested.OriginalXBindable.Value += deltaX; + nested.OriginalX += deltaX; }); return true; diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index cdd8bfbe50..0b8c0e28a7 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// This value is the original value plus the offset applied by the beatmap processing. /// Use if a value not affected by the offset is desired. /// - public float EffectiveX => OriginalXBindable.Value + XOffsetBindable.Value; + public float EffectiveX => OriginalX + XOffset; public double TimePreempt { get; set; } = 1000; From 93ef783339908aab6e10b0a5dbc530a67c0fa7c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 16:40:23 +0900 Subject: [PATCH 1333/1621] Remove BindableList usage --- .../Skinning/RulesetSkinProvidingContainer.cs | 18 +++-- osu.Game/Skinning/SkinProvidingContainer.cs | 77 ++++++------------- 2 files changed, 37 insertions(+), 58 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 19efc66814..4dea1ff51e 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.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 JetBrains.Annotations; using osu.Framework.Allocation; @@ -72,31 +73,36 @@ namespace osu.Game.Skinning protected virtual void UpdateSkinSources() { - SkinSources.Clear(); + ResetSources(); + + var skinSources = new List(); foreach (var skin in parentSource.AllSources) { switch (skin) { case LegacySkin legacySkin: - SkinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); + skinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); break; default: - SkinSources.Add(skin); + skinSources.Add(skin); break; } } - int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType().LastOrDefault()); + int lastDefaultSkinIndex = skinSources.IndexOf(skinSources.OfType().LastOrDefault()); // Ruleset resources should be given the ability to override game-wide defaults // This is achieved by placing them before the last instance of DefaultSkin. // Note that DefaultSkin may not be present in some test scenes. if (lastDefaultSkinIndex >= 0) - SkinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); + skinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); else - SkinSources.Add(rulesetResourcesSkin); + skinSources.Add(rulesetResourcesSkin); + + foreach (var skin in skinSources) + AddSource(skin); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index c83c299723..b1ada54a26 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -27,13 +26,13 @@ namespace osu.Game.Skinning /// /// Skins which should be exposed by this container, in order of lookup precedence. /// - protected readonly BindableList SkinSources = new BindableList(); + protected IEnumerable SkinSources => skinSources.Keys; /// /// 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(); + private readonly Dictionary skinSources = new Dictionary(); [CanBeNull] private ISkinSource fallbackSource; @@ -60,7 +59,7 @@ namespace osu.Game.Skinning : this() { if (skin != null) - SkinSources.Add(skin); + AddSource(skin); } /// @@ -70,61 +69,35 @@ namespace osu.Game.Skinning protected SkinProvidingContainer() { RelativeSizeAxes = Axes.Both; + } - SkinSources.BindCollectionChanged(((_, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - foreach (var skin in args.NewItems.Cast()) - { - disableableSkinSources.Add(skin, new DisableableSkinSource(skin, this)); + public void AddSource(ISkin skin) + { + skinSources.Add(skin, new DisableableSkinSource(skin, this)); - if (skin is ISkinSource source) - source.SourceChanged += OnSourceChanged; - } + if (skin is ISkinSource source) + source.SourceChanged += OnSourceChanged; + } - break; + public void RemoveSource(ISkin skin) + { + skinSources.Remove(skin); - case NotifyCollectionChangedAction.Reset: - case NotifyCollectionChangedAction.Remove: - foreach (var skin in args.OldItems.Cast()) - { - disableableSkinSources.Remove(skin); + if (skin is ISkinSource source) + source.SourceChanged += OnSourceChanged; + } - if (skin is ISkinSource source) - source.SourceChanged -= OnSourceChanged; - } - - break; - - case NotifyCollectionChangedAction.Replace: - foreach (var skin in args.OldItems.Cast()) - { - disableableSkinSources.Remove(skin); - - 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; - } - }), true); + public void ResetSources() + { + foreach (var skin in AllSources.ToArray()) + RemoveSource(skin); } public ISkin FindProvider(Func lookupFunction) { foreach (var skin in SkinSources) { - if (lookupFunction(disableableSkinSources[skin])) + if (lookupFunction(skinSources[skin])) return skin; } @@ -151,7 +124,7 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) { Drawable sourceDrawable; - if ((sourceDrawable = disableableSkinSources[skin]?.GetDrawableComponent(component)) != null) + if ((sourceDrawable = skinSources[skin]?.GetDrawableComponent(component)) != null) return sourceDrawable; } @@ -163,7 +136,7 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) { Texture sourceTexture; - if ((sourceTexture = disableableSkinSources[skin]?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) + if ((sourceTexture = skinSources[skin]?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) return sourceTexture; } @@ -175,7 +148,7 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) { ISample sourceSample; - if ((sourceSample = disableableSkinSources[skin]?.GetSample(sampleInfo)) != null) + if ((sourceSample = skinSources[skin]?.GetSample(sampleInfo)) != null) return sourceSample; } @@ -187,7 +160,7 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) { IBindable bindable; - if ((bindable = disableableSkinSources[skin]?.GetConfig(lookup)) != null) + if ((bindable = skinSources[skin]?.GetConfig(lookup)) != null) return bindable; } From 7ef7c5148f21c35426a45f4ae02d6a60755fc584 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 16:19:31 +0900 Subject: [PATCH 1334/1621] Add `ScrollingPath` for visualization of the real path of a `JuiceStream` --- .../Blueprints/Components/ScrollingPath.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs new file mode 100644 index 0000000000..337b8de92e --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs @@ -0,0 +1,79 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Lines; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components +{ + public class ScrollingPath : CompositeDrawable + { + private readonly Path drawablePath; + + private readonly List<(double Distance, float X)> vertices = new List<(double, float)>(); + + public ScrollingPath() + { + Anchor = Anchor.BottomLeft; + + InternalChildren = new Drawable[] + { + drawablePath = new SmoothPath + { + PathRadius = 2, + Alpha = 0.5f + }, + }; + } + + public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject) + { + X = hitObject.OriginalX; + Y = hitObjectContainer.PositionAtTime(hitObject.StartTime); + } + + public void UpdatePathFrom(ScrollingHitObjectContainer hitObjectContainer, JuiceStream hitObject) + { + double distanceToYFactor = -hitObjectContainer.LengthAtTime(hitObject.StartTime, hitObject.StartTime + 1 / hitObject.Velocity); + + computeDistanceXs(hitObject); + drawablePath.Vertices = vertices + .Select(v => new Vector2(v.X, (float)(v.Distance * distanceToYFactor))) + .ToArray(); + drawablePath.OriginPosition = drawablePath.PositionInBoundingBox(Vector2.Zero); + } + + private void computeDistanceXs(JuiceStream hitObject) + { + vertices.Clear(); + + var sliderVertices = new List(); + hitObject.Path.GetPathToProgress(sliderVertices, 0, 1); + + if (sliderVertices.Count == 0) + return; + + double distance = 0; + Vector2 lastPosition = Vector2.Zero; + + for (int repeat = 0; repeat < hitObject.RepeatCount + 1; repeat++) + { + foreach (var position in sliderVertices) + { + distance += Vector2.Distance(lastPosition, position); + lastPosition = position; + + vertices.Add((distance, position.X)); + } + + sliderVertices.Reverse(); + } + } + } +} From 0fa7716ceda7f8de41b1bf744bb5e98de3ff0d49 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 16:46:12 +0900 Subject: [PATCH 1335/1621] Show path of juice stream in selection blueprint --- .../JuiceStreamSelectionBlueprint.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index d6b8c35a09..9f9d6a0556 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -3,7 +3,9 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Caching; using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects; using osuTK; @@ -17,9 +19,14 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints private float minNestedX; private float maxNestedX; + private readonly ScrollingPath scrollingPath; + + private readonly Cached pathCache = new Cached(); + public JuiceStreamSelectionBlueprint(JuiceStream hitObject) : base(hitObject) { + InternalChild = scrollingPath = new ScrollingPath(); } [BackgroundDependencyLoader] @@ -29,7 +36,25 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints computeObjectBounds(); } - private void onDefaultsApplied(HitObject _) => computeObjectBounds(); + protected override void Update() + { + base.Update(); + + if (!IsSelected) return; + + scrollingPath.UpdatePositionFrom(HitObjectContainer, HitObject); + + if (pathCache.IsValid) return; + + scrollingPath.UpdatePathFrom(HitObjectContainer, HitObject); + pathCache.Validate(); + } + + private void onDefaultsApplied(HitObject _) + { + computeObjectBounds(); + pathCache.Invalidate(); + } private void computeObjectBounds() { From 935fbe7cc627f78deba63f33f1b0057d0470eb7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 16:41:48 +0900 Subject: [PATCH 1336/1621] Remove double fetch/binding of parent source --- .../Skinning/RulesetSkinProvidingContainer.cs | 17 +++---- osu.Game/Skinning/SkinProvidingContainer.cs | 44 ++++++++++++------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 4dea1ff51e..d5be5d9394 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -47,22 +48,19 @@ namespace osu.Game.Skinning }; } - private ISkinSource parentSource; - private ResourceStoreBackedSkin rulesetResourcesSkin; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - parentSource = parent.Get(); - parentSource.SourceChanged += OnSourceChanged; - if (Ruleset.CreateResourceStore() is IResourceStore resources) rulesetResourcesSkin = new ResourceStoreBackedSkin(resources, parent.Get(), parent.Get()); + var dependencies = base.CreateChildDependencies(parent); + // ensure sources are populated and ready for use before childrens' asynchronous load flow. UpdateSkinSources(); - return base.CreateChildDependencies(parent); + return dependencies; } protected override void OnSourceChanged() @@ -77,7 +75,9 @@ namespace osu.Game.Skinning var skinSources = new List(); - foreach (var skin in parentSource.AllSources) + Debug.Assert(ParentSource != null); + + foreach (var skin in ParentSource.AllSources) { switch (skin) { @@ -121,9 +121,6 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - if (parentSource != null) - parentSource.SourceChanged -= OnSourceChanged; - rulesetResourcesSkin?.Dispose(); } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index b1ada54a26..a1e71502b9 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Skinning private readonly Dictionary skinSources = new Dictionary(); [CanBeNull] - private ISkinSource fallbackSource; + protected ISkinSource ParentSource { get; private set; } /// /// Whether falling back to parent s is allowed in this container. @@ -101,7 +101,10 @@ namespace osu.Game.Skinning return skin; } - return fallbackSource?.FindProvider(lookupFunction); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.FindProvider(lookupFunction); } public IEnumerable AllSources @@ -111,9 +114,9 @@ namespace osu.Game.Skinning foreach (var skin in SkinSources) yield return skin; - if (fallbackSource != null) + if (AllowFallingBackToParent && ParentSource != null) { - foreach (var skin in fallbackSource.AllSources) + foreach (var skin in ParentSource.AllSources) yield return skin; } } @@ -128,7 +131,10 @@ namespace osu.Game.Skinning return sourceDrawable; } - return fallbackSource?.GetDrawableComponent(component); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.GetDrawableComponent(component); } public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) @@ -140,7 +146,10 @@ namespace osu.Game.Skinning return sourceTexture; } - return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.GetTexture(componentName, wrapModeS, wrapModeT); } public ISample GetSample(ISampleInfo sampleInfo) @@ -152,7 +161,10 @@ namespace osu.Game.Skinning return sourceSample; } - return fallbackSource?.GetSample(sampleInfo); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.GetSample(sampleInfo); } public IBindable GetConfig(TLookup lookup) @@ -164,7 +176,10 @@ namespace osu.Game.Skinning return bindable; } - return fallbackSource?.GetConfig(lookup); + if (!AllowFallingBackToParent) + return null; + + return ParentSource?.GetConfig(lookup); } protected virtual void OnSourceChanged() => SourceChanged?.Invoke(); @@ -173,12 +188,9 @@ namespace osu.Game.Skinning { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - if (AllowFallingBackToParent) - { - fallbackSource = dependencies.Get(); - if (fallbackSource != null) - fallbackSource.SourceChanged += OnSourceChanged; - } + ParentSource = dependencies.Get(); + if (ParentSource != null) + ParentSource.SourceChanged += OnSourceChanged; dependencies.CacheAs(this); @@ -192,8 +204,8 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); - if (fallbackSource != null) - fallbackSource.SourceChanged -= OnSourceChanged; + if (ParentSource != null) + ParentSource.SourceChanged -= OnSourceChanged; foreach (var source in SkinSources.OfType()) source.SourceChanged -= OnSourceChanged; From ec1224218c86587eb62137a96f4cd328ff0dcd35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 16:57:19 +0900 Subject: [PATCH 1337/1621] Localise source changed flow for better clarity --- osu.Game/Skinning/SkinProvidingContainer.cs | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index a1e71502b9..5beadd7178 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -76,7 +76,7 @@ namespace osu.Game.Skinning skinSources.Add(skin, new DisableableSkinSource(skin, this)); if (skin is ISkinSource source) - source.SourceChanged += OnSourceChanged; + source.SourceChanged += anySourceChanged; } public void RemoveSource(ISkin skin) @@ -84,7 +84,7 @@ namespace osu.Game.Skinning skinSources.Remove(skin); if (skin is ISkinSource source) - source.SourceChanged += OnSourceChanged; + source.SourceChanged += anySourceChanged; } public void ResetSources() @@ -182,7 +182,10 @@ namespace osu.Game.Skinning return ParentSource?.GetConfig(lookup); } - protected virtual void OnSourceChanged() => SourceChanged?.Invoke(); + /// + /// Invoked when any source has changed (either or + /// + protected virtual void OnSourceChanged() { } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -190,13 +193,21 @@ namespace osu.Game.Skinning ParentSource = dependencies.Get(); if (ParentSource != null) - ParentSource.SourceChanged += OnSourceChanged; + ParentSource.SourceChanged += anySourceChanged; dependencies.CacheAs(this); return dependencies; } + private void anySourceChanged() + { + // Expose to implementations, giving them a chance to react before notifying external consumers. + OnSourceChanged(); + + SourceChanged?.Invoke(); + } + protected override void Dispose(bool isDisposing) { // Must be done before base.Dispose() @@ -205,10 +216,10 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); if (ParentSource != null) - ParentSource.SourceChanged -= OnSourceChanged; + ParentSource.SourceChanged -= anySourceChanged; foreach (var source in SkinSources.OfType()) - source.SourceChanged -= OnSourceChanged; + source.SourceChanged -= anySourceChanged; } private class DisableableSkinSource : ISkin From b4240d3ca4db7d06599524612486f125f1a01f0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:04:59 +0900 Subject: [PATCH 1338/1621] Simplify lookups to avoid a second dictionary fetch --- osu.Game/Skinning/SkinProvidingContainer.cs | 33 ++++++++------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 5beadd7178..a6debcbf66 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -24,13 +24,7 @@ namespace osu.Game.Skinning public event Action SourceChanged; /// - /// Skins which should be exposed by this container, in order of lookup precedence. - /// - protected IEnumerable SkinSources => skinSources.Keys; - - /// - /// A dictionary mapping each from the - /// to one that performs the "allow lookup" checks before proceeding with a lookup. + /// A dictionary mapping each source to a wrapper which handles lookup allowances. /// private readonly Dictionary skinSources = new Dictionary(); @@ -64,7 +58,6 @@ namespace osu.Game.Skinning /// /// Constructs a new with no sources. - /// Implementations can add or change sources through the list. /// protected SkinProvidingContainer() { @@ -95,9 +88,9 @@ namespace osu.Game.Skinning public ISkin FindProvider(Func lookupFunction) { - foreach (var skin in SkinSources) + foreach (var (skin, lookupWrapper) in skinSources) { - if (lookupFunction(skinSources[skin])) + if (lookupFunction(lookupWrapper)) return skin; } @@ -111,7 +104,7 @@ namespace osu.Game.Skinning { get { - foreach (var skin in SkinSources) + foreach (var skin in skinSources.Keys) yield return skin; if (AllowFallingBackToParent && ParentSource != null) @@ -124,10 +117,10 @@ namespace osu.Game.Skinning public Drawable GetDrawableComponent(ISkinComponent component) { - foreach (var skin in SkinSources) + foreach (var (_, lookupWrapper) in skinSources) { Drawable sourceDrawable; - if ((sourceDrawable = skinSources[skin]?.GetDrawableComponent(component)) != null) + if ((sourceDrawable = lookupWrapper.GetDrawableComponent(component)) != null) return sourceDrawable; } @@ -139,10 +132,10 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { - foreach (var skin in SkinSources) + foreach (var (_, lookupWrapper) in skinSources) { Texture sourceTexture; - if ((sourceTexture = skinSources[skin]?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) + if ((sourceTexture = lookupWrapper.GetTexture(componentName, wrapModeS, wrapModeT)) != null) return sourceTexture; } @@ -154,10 +147,10 @@ namespace osu.Game.Skinning public ISample GetSample(ISampleInfo sampleInfo) { - foreach (var skin in SkinSources) + foreach (var (_, lookupWrapper) in skinSources) { ISample sourceSample; - if ((sourceSample = skinSources[skin]?.GetSample(sampleInfo)) != null) + if ((sourceSample = lookupWrapper.GetSample(sampleInfo)) != null) return sourceSample; } @@ -169,10 +162,10 @@ namespace osu.Game.Skinning public IBindable GetConfig(TLookup lookup) { - foreach (var skin in SkinSources) + foreach (var (_, lookupWrapper) in skinSources) { IBindable bindable; - if ((bindable = skinSources[skin]?.GetConfig(lookup)) != null) + if ((bindable = lookupWrapper.GetConfig(lookup)) != null) return bindable; } @@ -218,7 +211,7 @@ namespace osu.Game.Skinning if (ParentSource != null) ParentSource.SourceChanged -= anySourceChanged; - foreach (var source in SkinSources.OfType()) + foreach (var source in skinSources.Keys.OfType()) source.SourceChanged -= anySourceChanged; } From 1232925f93113846049c7be94a09b8cd14a76299 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:06:00 +0900 Subject: [PATCH 1339/1621] Make source manipulation methods `protected` --- 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 a6debcbf66..730659eacd 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } - public void AddSource(ISkin skin) + protected void AddSource(ISkin skin) { skinSources.Add(skin, new DisableableSkinSource(skin, this)); @@ -72,7 +72,7 @@ namespace osu.Game.Skinning source.SourceChanged += anySourceChanged; } - public void RemoveSource(ISkin skin) + protected void RemoveSource(ISkin skin) { skinSources.Remove(skin); @@ -80,7 +80,7 @@ namespace osu.Game.Skinning source.SourceChanged += anySourceChanged; } - public void ResetSources() + protected void ResetSources() { foreach (var skin in AllSources.ToArray()) RemoveSource(skin); From 032c285edefbefa8aa2874bca356f9b295123caf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:07:25 +0900 Subject: [PATCH 1340/1621] Move private downwards --- osu.Game/Skinning/SkinProvidingContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 730659eacd..6b6fdce480 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -23,11 +23,6 @@ namespace osu.Game.Skinning { public event Action SourceChanged; - /// - /// A dictionary mapping each source to a wrapper which handles lookup allowances. - /// - private readonly Dictionary skinSources = new Dictionary(); - [CanBeNull] protected ISkinSource ParentSource { get; private set; } @@ -46,6 +41,11 @@ namespace osu.Game.Skinning protected virtual bool AllowColourLookup => true; + /// + /// A dictionary mapping each source to a wrapper which handles lookup allowances. + /// + private readonly Dictionary skinSources = new Dictionary(); + /// /// Constructs a new initialised with a single skin source. /// From 7833a1b09a32b7e7061f44935da8e8107d981489 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 17:15:27 +0900 Subject: [PATCH 1341/1621] Allow `FruitOutline` to be used for nested hit objects --- .../Edit/Blueprints/Components/FruitOutline.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs index 8769acc382..345b59bdcd 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,7 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { Anchor = Anchor.BottomLeft; Origin = Anchor.Centre; - Size = new Vector2(2 * CatchHitObject.OBJECT_RADIUS); InternalChild = new BorderPiece(); } @@ -28,10 +28,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components Colour = osuColour.Yellow; } - public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject) + public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject, [CanBeNull] CatchHitObject parent = null) { - X = hitObject.EffectiveX; - Y = hitObjectContainer.PositionAtTime(hitObject.StartTime); + X = hitObject.EffectiveX - (parent?.OriginalX ?? 0); + Y = hitObjectContainer.PositionAtTime(hitObject.StartTime, parent?.StartTime ?? hitObjectContainer.Time.Current); Scale = new Vector2(hitObject.Scale); } } From 2ba300393495ab865ffe622a86772903ba809d9d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 17:15:51 +0900 Subject: [PATCH 1342/1621] Add nested fruit outlines to juice stream selection blueprint --- .../Components/NestedOutlineContainer.cs | 53 +++++++++++++++++++ .../JuiceStreamSelectionBlueprint.cs | 12 ++++- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs new file mode 100644 index 0000000000..9e1743de98 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs @@ -0,0 +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 System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components +{ + public class NestedOutlineContainer : CompositeDrawable + { + private readonly Container nestedOutlines; + + private readonly List nestedHitObjects = new List(); + + public NestedOutlineContainer() + { + Anchor = Anchor.BottomLeft; + + InternalChild = nestedOutlines = new Container(); + } + + public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject) + { + X = parentHitObject.OriginalX; + Y = hitObjectContainer.PositionAtTime(parentHitObject.StartTime); + } + + public void UpdateNestedObjectsFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject) + { + nestedHitObjects.Clear(); + nestedHitObjects.AddRange(parentHitObject.NestedHitObjects + .OfType() + .Where(h => !(h is TinyDroplet))); + + while (nestedHitObjects.Count < nestedOutlines.Count) + nestedOutlines.Remove(nestedOutlines[^1]); + + while (nestedOutlines.Count < nestedHitObjects.Count) + nestedOutlines.Add(new FruitOutline()); + + for (int i = 0; i < nestedHitObjects.Count; i++) + { + var hitObject = nestedHitObjects[i]; + nestedOutlines[i].UpdateFrom(hitObjectContainer, hitObject, parentHitObject); + nestedOutlines[i].Scale *= hitObject is Droplet ? 0.5f : 1; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index 9f9d6a0556..bf7b962e0a 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -4,6 +4,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Caching; +using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; @@ -21,12 +22,18 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints private readonly ScrollingPath scrollingPath; + private readonly NestedOutlineContainer nestedOutlineContainer; + private readonly Cached pathCache = new Cached(); public JuiceStreamSelectionBlueprint(JuiceStream hitObject) : base(hitObject) { - InternalChild = scrollingPath = new ScrollingPath(); + InternalChildren = new Drawable[] + { + scrollingPath = new ScrollingPath(), + nestedOutlineContainer = new NestedOutlineContainer() + }; } [BackgroundDependencyLoader] @@ -43,10 +50,13 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints if (!IsSelected) return; scrollingPath.UpdatePositionFrom(HitObjectContainer, HitObject); + nestedOutlineContainer.UpdatePositionFrom(HitObjectContainer, HitObject); if (pathCache.IsValid) return; scrollingPath.UpdatePathFrom(HitObjectContainer, HitObject); + nestedOutlineContainer.UpdateNestedObjectsFrom(HitObjectContainer, HitObject); + pathCache.Validate(); } From cd4885e4502feb0f28b3ea32c26fa13640e4c224 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:18:45 +0900 Subject: [PATCH 1343/1621] Add xmldoc and remove any question of how the intitial flow is being run --- .../Skinning/RulesetSkinProvidingContainer.cs | 28 ++++++------------- osu.Game/Skinning/SkinProvidingContainer.cs | 21 ++++++++++++-- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index d5be5d9394..f5a7788359 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -55,25 +55,15 @@ namespace osu.Game.Skinning if (Ruleset.CreateResourceStore() is IResourceStore resources) rulesetResourcesSkin = new ResourceStoreBackedSkin(resources, parent.Get(), parent.Get()); - var dependencies = base.CreateChildDependencies(parent); - - // ensure sources are populated and ready for use before childrens' asynchronous load flow. - UpdateSkinSources(); - - return dependencies; + return base.CreateChildDependencies(parent); } protected override void OnSourceChanged() - { - UpdateSkinSources(); - base.OnSourceChanged(); - } - - protected virtual void UpdateSkinSources() { ResetSources(); - var skinSources = new List(); + // Populate a local list first so we can adjust the returned order as we go. + var sources = new List(); Debug.Assert(ParentSource != null); @@ -82,26 +72,26 @@ namespace osu.Game.Skinning switch (skin) { case LegacySkin legacySkin: - skinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); + sources.Add(GetLegacyRulesetTransformedSkin(legacySkin)); break; default: - skinSources.Add(skin); + sources.Add(skin); break; } } - int lastDefaultSkinIndex = skinSources.IndexOf(skinSources.OfType().LastOrDefault()); + int lastDefaultSkinIndex = sources.IndexOf(sources.OfType().LastOrDefault()); // Ruleset resources should be given the ability to override game-wide defaults // This is achieved by placing them before the last instance of DefaultSkin. // Note that DefaultSkin may not be present in some test scenes. if (lastDefaultSkinIndex >= 0) - skinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); + sources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin); else - skinSources.Add(rulesetResourcesSkin); + sources.Add(rulesetResourcesSkin); - foreach (var skin in skinSources) + foreach (var skin in sources) AddSource(skin); } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 6b6fdce480..6033776979 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -64,6 +64,10 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } + /// + /// Add a new skin to this provider. Will be added to the end of the lookup order precedence. + /// + /// The skin to add. protected void AddSource(ISkin skin) { skinSources.Add(skin, new DisableableSkinSource(skin, this)); @@ -72,14 +76,22 @@ namespace osu.Game.Skinning source.SourceChanged += anySourceChanged; } + /// + /// Remove a skin from this provider. + /// + /// The skin to remove. protected void RemoveSource(ISkin skin) { - skinSources.Remove(skin); + if (!skinSources.Remove(skin)) + return; if (skin is ISkinSource source) - source.SourceChanged += anySourceChanged; + source.SourceChanged -= anySourceChanged; } + /// + /// Clears all skin sources. + /// protected void ResetSources() { foreach (var skin in AllSources.ToArray()) @@ -176,7 +188,8 @@ namespace osu.Game.Skinning } /// - /// Invoked when any source has changed (either or + /// Invoked when any source has changed (either or a source registered via ). + /// This is also invoked once initially during to ensure sources are ready for children consumption. /// protected virtual void OnSourceChanged() { } @@ -190,6 +203,8 @@ namespace osu.Game.Skinning dependencies.CacheAs(this); + anySourceChanged(); + return dependencies; } From d75d67577a1668749ffcb08600ce1440fc503256 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 17:37:34 +0900 Subject: [PATCH 1344/1621] Fix regressed tests --- .../Visual/Gameplay/TestSceneSkinnableDrawable.cs | 2 +- osu.Game/Skinning/BeatmapSkinProvidingContainer.cs | 6 +++--- osu.Game/Skinning/SkinProvidingContainer.cs | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 02b1959dab..3e8ba69e01 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void Disable() { allow = false; - OnSourceChanged(); + TriggerSourceChanged(); } public SwitchableSkinProvidingContainer(ISkin skin) diff --git a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs index f12f44e347..57c08a903f 100644 --- a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs +++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs @@ -83,9 +83,9 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load() { - beatmapSkins.BindValueChanged(_ => OnSourceChanged()); - beatmapColours.BindValueChanged(_ => OnSourceChanged()); - beatmapHitsounds.BindValueChanged(_ => OnSourceChanged()); + beatmapSkins.BindValueChanged(_ => TriggerSourceChanged()); + beatmapColours.BindValueChanged(_ => TriggerSourceChanged()); + beatmapHitsounds.BindValueChanged(_ => TriggerSourceChanged()); } } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 6033776979..d2f38b58a4 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -73,7 +73,7 @@ namespace osu.Game.Skinning skinSources.Add(skin, new DisableableSkinSource(skin, this)); if (skin is ISkinSource source) - source.SourceChanged += anySourceChanged; + source.SourceChanged += TriggerSourceChanged; } /// @@ -86,7 +86,7 @@ namespace osu.Game.Skinning return; if (skin is ISkinSource source) - source.SourceChanged -= anySourceChanged; + source.SourceChanged -= TriggerSourceChanged; } /// @@ -199,16 +199,16 @@ namespace osu.Game.Skinning ParentSource = dependencies.Get(); if (ParentSource != null) - ParentSource.SourceChanged += anySourceChanged; + ParentSource.SourceChanged += TriggerSourceChanged; dependencies.CacheAs(this); - anySourceChanged(); + TriggerSourceChanged(); return dependencies; } - private void anySourceChanged() + protected void TriggerSourceChanged() { // Expose to implementations, giving them a chance to react before notifying external consumers. OnSourceChanged(); @@ -224,10 +224,10 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); if (ParentSource != null) - ParentSource.SourceChanged -= anySourceChanged; + ParentSource.SourceChanged -= TriggerSourceChanged; foreach (var source in skinSources.Keys.OfType()) - source.SourceChanged -= anySourceChanged; + source.SourceChanged -= TriggerSourceChanged; } private class DisableableSkinSource : ISkin From 8b12ec9586019b42b01bd98e861ca0f5f2e31a3c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Jul 2021 17:52:27 +0900 Subject: [PATCH 1345/1621] Fix intermittent ready button test failures --- .../Multiplayer/TestSceneMultiplayerReadyButton.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 0e036e8868..c36e1200e6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -113,10 +113,10 @@ namespace osu.Game.Tests.Visual.Multiplayer }); addClickButtonStep(); - AddAssert("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); + AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); addClickButtonStep(); - AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); + AddUntilStep("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); } [TestCase(true)] @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); addClickButtonStep(); - AddAssert("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); + AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); verifyGameplayStartFlow(); } @@ -206,8 +206,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private void verifyGameplayStartFlow() { + AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); addClickButtonStep(); - AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad); + AddUntilStep("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad); AddAssert("ready button disabled", () => !button.ChildrenOfType().Single().Enabled.Value); AddStep("transitioned to gameplay", () => readyClickOperation.Dispose()); From b209868d96d277740c8718bb49d3e1110c1d92b5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Jul 2021 17:57:24 +0900 Subject: [PATCH 1346/1621] Fix another potential failure --- .../Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index c36e1200e6..820b403a10 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -219,7 +219,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.FinishedPlay); }); - AddAssert("ready button enabled", () => button.ChildrenOfType().Single().Enabled.Value); + AddUntilStep("ready button enabled", () => button.ChildrenOfType().Single().Enabled.Value); } } } From 32ef2405c4783f5a2db54f80bdfa5f24782a383c Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 6 Jul 2021 11:30:56 +0200 Subject: [PATCH 1347/1621] Use null instead of -1 --- .../SelectionCycleFillFlowContainer.cs | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 0e7b2bcc05..19a423c25b 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -13,43 +13,51 @@ namespace osu.Game.Graphics.Containers /// public class SelectionCycleFillFlowContainer : FillFlowContainer where T : Drawable, ISelectable { - private int selectedIndex = -1; + private int? selectedIndex; - private void setSelected(int value) + private void setSelected(int? value) { if (selectedIndex == value) return; // Deselect the previously-selected button - if (selectedIndex != -1) - this[selectedIndex].Selected = false; + if (selectedIndex.HasValue) + this[selectedIndex.Value].Selected = false; selectedIndex = value; // Select the newly-selected button - if (selectedIndex != -1) - this[selectedIndex].Selected = true; + if (selectedIndex.HasValue) + this[selectedIndex.Value].Selected = true; } public void SelectNext() { - if (selectedIndex == -1 || selectedIndex == Count - 1) + if (!selectedIndex.HasValue || selectedIndex == Count - 1) setSelected(0); else - setSelected(selectedIndex + 1); + setSelected(selectedIndex.Value + 1); } public void SelectPrevious() { - if (selectedIndex == -1 || selectedIndex == 0) + if (!selectedIndex.HasValue || selectedIndex == 0) setSelected(Count - 1); else - setSelected(selectedIndex - 1); + setSelected(selectedIndex.Value - 1); } - public void Deselect() => setSelected(-1); - public void Select(T item) => setSelected(IndexOf(item)); + public void Deselect() => setSelected(null); + public void Select(T item) + { + var newIndex = IndexOf(item); - public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex] : null; + if (newIndex < 0) + setSelected(null); + else + setSelected(IndexOf(item)); + } + + public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex.Value] : null; } } From c5a0672277514f27cd2fb84b99669cf9c80f91c4 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 6 Jul 2021 12:07:25 +0200 Subject: [PATCH 1348/1621] Use IStateful instead of ISelected --- .../SelectionCycleFillFlowContainer.cs | 7 ++-- .../Graphics/UserInterface/DialogButton.cs | 35 ++++++++++++------- .../Graphics/UserInterface/ISelectable.cs | 10 ------ osu.Game/Overlays/Volume/VolumeMeter.cs | 32 +++++++++++------ osu.Game/Overlays/VolumeOverlay.cs | 7 ++-- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 8 ++--- 6 files changed, 56 insertions(+), 43 deletions(-) delete mode 100644 osu.Game/Graphics/UserInterface/ISelectable.cs diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 19a423c25b..5849fbbe30 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.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 osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -11,7 +12,7 @@ namespace osu.Game.Graphics.Containers /// A FillFlowContainer that provides functionality to cycle selection between children /// The selection wraps around when overflowing past the first or last child. /// - public class SelectionCycleFillFlowContainer : FillFlowContainer where T : Drawable, ISelectable + public class SelectionCycleFillFlowContainer : FillFlowContainer where T : Drawable, IStateful { private int? selectedIndex; @@ -22,13 +23,13 @@ namespace osu.Game.Graphics.Containers // Deselect the previously-selected button if (selectedIndex.HasValue) - this[selectedIndex.Value].Selected = false; + this[selectedIndex.Value].State = SelectionState.NotSelected; selectedIndex = value; // Select the newly-selected button if (selectedIndex.HasValue) - this[selectedIndex.Value].Selected = true; + this[selectedIndex.Value].State = SelectionState.Selected; } public void SelectNext() diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 718a5171c2..f9fbec2b48 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -1,6 +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 osu.Framework; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -19,7 +21,7 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { - public class DialogButton : OsuClickableContainer, ISelectable + public class DialogButton : OsuClickableContainer, IStateful { private const float idle_width = 0.8f; private const float hover_width = 0.9f; @@ -27,12 +29,21 @@ namespace osu.Game.Graphics.UserInterface private const float hover_duration = 500; private const float click_duration = 200; - public readonly BindableBool SelectedBindable = new BindableBool(); + public event Action StateChanged; - public bool Selected + private SelectionState state; + + public SelectionState State { - get => SelectedBindable.Value; - set => SelectedBindable.Value = value; + get => state; + set + { + if (state == value) + return; + + state = value; + StateChanged?.Invoke(value); + } } private readonly Container backgroundContainer; @@ -159,7 +170,7 @@ namespace osu.Game.Graphics.UserInterface updateGlow(); - SelectedBindable.ValueChanged += selectionChanged; + StateChanged += selectionChanged; } private Color4 buttonColour; @@ -227,7 +238,7 @@ namespace osu.Game.Graphics.UserInterface .OnComplete(_ => { clickAnimating = false; - SelectedBindable.TriggerChange(); + StateChanged?.Invoke(State); }); return base.OnClick(e); @@ -241,7 +252,7 @@ namespace osu.Game.Graphics.UserInterface protected override void OnMouseUp(MouseUpEvent e) { - if (Selected) + if (State == SelectionState.Selected) colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In); base.OnMouseUp(e); } @@ -249,7 +260,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { base.OnHover(e); - Selected = true; + State = SelectionState.Selected; return true; } @@ -257,15 +268,15 @@ namespace osu.Game.Graphics.UserInterface protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - Selected = false; + State = SelectionState.NotSelected; } - private void selectionChanged(ValueChangedEvent args) + private void selectionChanged(SelectionState newState) { if (clickAnimating) return; - if (args.NewValue) + if (newState == SelectionState.Selected) { spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); colourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutElastic); diff --git a/osu.Game/Graphics/UserInterface/ISelectable.cs b/osu.Game/Graphics/UserInterface/ISelectable.cs deleted file mode 100644 index 49c3d725c8..0000000000 --- a/osu.Game/Graphics/UserInterface/ISelectable.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 interface ISelectable - { - bool Selected { get; set; } - } -} diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 3e9849a077..a7c4fb6e7d 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -3,6 +3,7 @@ using System; using System.Globalization; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -26,7 +27,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Volume { - public class VolumeMeter : Container, IKeyBindingHandler, ISelectable + public class VolumeMeter : Container, IKeyBindingHandler, IStateful { private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -44,12 +45,21 @@ namespace osu.Game.Overlays.Volume private Sample sample; private double sampleLastPlaybackTime; - public BindableBool SelectedBindable = new BindableBool(); + public event Action StateChanged; - public bool Selected + private SelectionState state; + + public SelectionState State { - get => SelectedBindable.Value; - set => SelectedBindable.Value = value; + get => state; + set + { + if (state == value) + return; + + state = value; + StateChanged?.Invoke(value); + } } public VolumeMeter(string name, float circleSize, Color4 meterColour) @@ -220,7 +230,7 @@ namespace osu.Game.Overlays.Volume bgProgress.Current.Value = 0.75f; - SelectedBindable.ValueChanged += selectionChanged; + StateChanged += stateChanged; } private int? displayVolumeInt; @@ -341,7 +351,7 @@ namespace osu.Game.Overlays.Volume protected override bool OnHover(HoverEvent e) { - Selected = true; + State = SelectionState.Selected; return false; } @@ -349,9 +359,9 @@ namespace osu.Game.Overlays.Volume { } - private void selectionChanged(ValueChangedEvent selected) + private void stateChanged(SelectionState newState) { - if (selected.NewValue) + if (newState == SelectionState.Selected) { this.ScaleTo(1.04f, transition_length, Easing.OutExpo); focusGlowContainer.FadeIn(transition_length, Easing.OutExpo); @@ -371,12 +381,12 @@ namespace osu.Game.Overlays.Volume switch (action) { case GlobalAction.SelectPrevious: - Selected = true; + State = SelectionState.Selected; adjust(1, false); return true; case GlobalAction.SelectNext: - Selected = true; + State = SelectionState.Selected; adjust(-1, false); return true; } diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 0c56e48cd4..56aa62d6e4 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -13,6 +13,7 @@ using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays.Volume; using osuTK; @@ -93,7 +94,7 @@ namespace osu.Game.Overlays foreach (var volumeMeter in volumeMeters) { volumeMeter.Bindable.ValueChanged += _ => Show(); - volumeMeter.SelectedBindable.ValueChanged += selected => volumeMeterSelectionChanged(volumeMeter, selected.NewValue); + volumeMeter.StateChanged += selected => volumeMeterSelectionChanged(volumeMeter, selected); } muteButton.Current.ValueChanged += _ => Show(); @@ -140,9 +141,9 @@ namespace osu.Game.Overlays return false; } - private void volumeMeterSelectionChanged(VolumeMeter meter, bool isSelected) + private void volumeMeterSelectionChanged(VolumeMeter meter, SelectionState state) { - if (!isSelected) + if (state == SelectionState.NotSelected) volumeMeters.Deselect(); else volumeMeters.Select(meter); diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 876f33d814..32f7a93cef 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -184,7 +184,7 @@ namespace osu.Game.Screens.Play } }; - button.SelectedBindable.ValueChanged += selected => buttonSelectionChanged(button, selected.NewValue); + button.StateChanged += selected => buttonSelectionChanged(button, selected); InternalButtons.Add(button); } @@ -217,9 +217,9 @@ namespace osu.Game.Screens.Play { } - private void buttonSelectionChanged(DialogButton button, bool isSelected) + private void buttonSelectionChanged(DialogButton button, SelectionState state) { - if (!isSelected) + if (state == SelectionState.NotSelected) InternalButtons.Deselect(); else InternalButtons.Select(button); @@ -263,7 +263,7 @@ namespace osu.Game.Screens.Play protected override bool OnMouseMove(MouseMoveEvent e) { - Selected = true; + State = SelectionState.Selected; return base.OnMouseMove(e); } } From 7b21d1ecf9ac251e697d65e626044ed3d5244e2c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 6 Jul 2021 19:50:32 +0900 Subject: [PATCH 1349/1621] Fix juice stream outline disappears away when start position is outside the screen. --- .../Components/NestedOutlineContainer.cs | 20 +++++++++---------- .../Blueprints/Components/ScrollingPath.cs | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs index 9e1743de98..48d90e8b24 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.UI.Scrolling; @@ -12,15 +13,11 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { public class NestedOutlineContainer : CompositeDrawable { - private readonly Container nestedOutlines; - private readonly List nestedHitObjects = new List(); public NestedOutlineContainer() { Anchor = Anchor.BottomLeft; - - InternalChild = nestedOutlines = new Container(); } public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject) @@ -36,18 +33,21 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components .OfType() .Where(h => !(h is TinyDroplet))); - while (nestedHitObjects.Count < nestedOutlines.Count) - nestedOutlines.Remove(nestedOutlines[^1]); + while (nestedHitObjects.Count < InternalChildren.Count) + RemoveInternal(InternalChildren[^1]); - while (nestedOutlines.Count < nestedHitObjects.Count) - nestedOutlines.Add(new FruitOutline()); + while (InternalChildren.Count < nestedHitObjects.Count) + AddInternal(new FruitOutline()); for (int i = 0; i < nestedHitObjects.Count; i++) { var hitObject = nestedHitObjects[i]; - nestedOutlines[i].UpdateFrom(hitObjectContainer, hitObject, parentHitObject); - nestedOutlines[i].Scale *= hitObject is Droplet ? 0.5f : 1; + var outline = (FruitOutline)InternalChildren[i]; + outline.UpdateFrom(hitObjectContainer, hitObject, parentHitObject); + outline.Scale *= hitObject is Droplet ? 0.5f : 1; } } + + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs index 337b8de92e..96111beda4 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -75,5 +76,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components sliderVertices.Reverse(); } } + + // Because this has 0x0 size, the contents are otherwise masked away if the start position is outside the screen. + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; } } From 06d2c6f0a1916dd30c673004713d4515ab2f4944 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 19:51:57 +0900 Subject: [PATCH 1350/1621] 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 9067ae1cd9..cfc28ffb21 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 626be76a84..646d21dfee 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index e339e49187..f36f7881cd 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 07d54d261a0359b7a2df15e685ee0874b2cbb4bf Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 6 Jul 2021 13:24:18 +0200 Subject: [PATCH 1351/1621] Let selection container handle manual selection changes --- .../SelectionCycleFillFlowContainer.cs | 31 +++++++++++++++++++ osu.Game/Overlays/VolumeOverlay.cs | 12 ------- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 12 ------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 5849fbbe30..b48a697903 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -1,6 +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.Collections.Generic; using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -60,5 +62,34 @@ namespace osu.Game.Graphics.Containers } public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex.Value] : null; + + private readonly Dictionary> handlerMap = new Dictionary>(); + + public override void Add(T drawable) + { + // This event is used to update selection state when modified within the drawable itself. + // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container + drawable.StateChanged += handlerMap[drawable] = state => selectionChanged(drawable, state); + + base.Add(drawable); + } + + public override bool Remove(T drawable) + { + if (!base.Remove(drawable)) + return false; + + drawable.StateChanged -= handlerMap[drawable]; + handlerMap.Remove(drawable); + return true; + } + + private void selectionChanged(T drawable, SelectionState state) + { + if (state == SelectionState.NotSelected) + Deselect(); + else + Select(drawable); + } } } diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 56aa62d6e4..a96949e96f 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -13,7 +13,6 @@ using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays.Volume; using osuTK; @@ -92,10 +91,7 @@ namespace osu.Game.Overlays base.LoadComplete(); foreach (var volumeMeter in volumeMeters) - { volumeMeter.Bindable.ValueChanged += _ => Show(); - volumeMeter.StateChanged += selected => volumeMeterSelectionChanged(volumeMeter, selected); - } muteButton.Current.ValueChanged += _ => Show(); } @@ -141,14 +137,6 @@ namespace osu.Game.Overlays return false; } - private void volumeMeterSelectionChanged(VolumeMeter meter, SelectionState state) - { - if (state == SelectionState.NotSelected) - volumeMeters.Deselect(); - else - volumeMeters.Select(meter); - } - private ScheduledDelegate popOutDelegate; public override void Show() diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 32f7a93cef..c71dd2ce65 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -184,8 +183,6 @@ namespace osu.Game.Screens.Play } }; - button.StateChanged += selected => buttonSelectionChanged(button, selected); - InternalButtons.Add(button); } @@ -216,15 +213,6 @@ namespace osu.Game.Screens.Play public void OnReleased(GlobalAction action) { } - - private void buttonSelectionChanged(DialogButton button, SelectionState state) - { - if (state == SelectionState.NotSelected) - InternalButtons.Deselect(); - else - InternalButtons.Select(button); - } - private void updateRetryCount() { // "You've retried 1,065 times in this session" From 6bc00208251091726aa3285cc6c6863b886942ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Jul 2021 20:28:49 +0900 Subject: [PATCH 1352/1621] Fix intermittent spectate button test failures --- .../Multiplayer/TestSceneMultiplayerSpectateButton.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 4966dfbe50..3d08d5da9e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -122,10 +122,10 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestToggleWhenIdle(MultiplayerUserState initialState) { addClickSpectateButtonStep(); - AddAssert("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating); + AddUntilStep("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating); addClickSpectateButtonStep(); - AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); + AddUntilStep("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); } [TestCase(MultiplayerRoomState.Closed)] @@ -174,9 +174,9 @@ namespace osu.Game.Tests.Visual.Multiplayer }); private void assertSpectateButtonEnablement(bool shouldBeEnabled) - => AddAssert($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); + => AddUntilStep($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); private void assertReadyButtonEnablement(bool shouldBeEnabled) - => AddAssert($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); + => AddUntilStep($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); } } From ffe18ebe516e20ea367e836908d51d435d455f65 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 6 Jul 2021 14:11:46 +0200 Subject: [PATCH 1353/1621] Resolve build errors --- .../Gameplay/TestSceneGameplayMenuOverlay.cs | 34 +++++++++---------- .../SelectionCycleFillFlowContainer.cs | 12 +++++-- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 1 + 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index 0aafbda951..d6a50fc346 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Gameplay showOverlay(); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected); + AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().State == SelectionState.Selected); } /// @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Gameplay showOverlay(); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => getButton(0).Selected); + AddAssert("First button selected", () => getButton(0).State == SelectionState.Selected); } /// @@ -111,11 +111,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Show overlay", () => failOverlay.Show()); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().State == SelectionState.Selected); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddAssert("First button selected", () => failOverlay.Buttons.First().State == SelectionState.Selected); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().State == SelectionState.Selected); } /// @@ -127,11 +127,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Show overlay", () => failOverlay.Show()); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddAssert("First button selected", () => failOverlay.Buttons.First().State == SelectionState.Selected); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().State == SelectionState.Selected); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddAssert("First button selected", () => failOverlay.Buttons.First().State == SelectionState.Selected); } /// @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First())); AddStep("Hide overlay", () => failOverlay.Hide()); - AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected)); + AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.State == SelectionState.Selected)); } /// @@ -162,11 +162,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hide overlay", () => pauseOverlay.Hide()); showOverlay(); - AddAssert("First button not selected", () => !getButton(0).Selected); + AddAssert("First button not selected", () => getButton(0).State == SelectionState.NotSelected); AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1))); - AddAssert("First button selected", () => getButton(0).Selected); + AddAssert("First button selected", () => getButton(0).State == SelectionState.Selected); } /// @@ -179,8 +179,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Down arrow", () => InputManager.Key(Key.Down)); AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); - AddAssert("First button not selected", () => !getButton(0).Selected); - AddAssert("Second button selected", () => getButton(1).Selected); + AddAssert("First button not selected", () => getButton(0).State == SelectionState.NotSelected); + AddAssert("Second button selected", () => getButton(1).State == SelectionState.Selected); } /// @@ -196,8 +196,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Up arrow", () => InputManager.Key(Key.Up)); - AddAssert("Second button not selected", () => !getButton(1).Selected); - AddAssert("First button selected", () => getButton(0).Selected); + AddAssert("Second button not selected", () => getButton(1).State == SelectionState.NotSelected); + AddAssert("First button selected", () => getButton(0).State == SelectionState.Selected); } /// @@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero)); AddStep("Down arrow", () => InputManager.Key(Key.Down)); - AddAssert("First button selected", () => getButton(0).Selected); // Initial state condition + AddAssert("First button selected", () => getButton(0).State == SelectionState.Selected); // Initial state condition } /// @@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.Gameplay showOverlay(); AddAssert("No button selected", - () => pauseOverlay.Buttons.All(button => !button.Selected)); + () => pauseOverlay.Buttons.All(button => button.State == SelectionState.NotSelected)); } private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show()); diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index b48a697903..3dbe1f8f47 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -69,7 +69,9 @@ namespace osu.Game.Graphics.Containers { // This event is used to update selection state when modified within the drawable itself. // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container - drawable.StateChanged += handlerMap[drawable] = state => selectionChanged(drawable, state); + handlerMap[drawable] = state => selectionChanged(drawable, state); + + drawable.StateChanged += handlerMap[drawable]; base.Add(drawable); } @@ -79,8 +81,12 @@ namespace osu.Game.Graphics.Containers if (!base.Remove(drawable)) return false; - drawable.StateChanged -= handlerMap[drawable]; - handlerMap.Remove(drawable); + if (handlerMap.TryGetValue(drawable, out var action)) + { + drawable.StateChanged -= action; + handlerMap.Remove(drawable); + } + return true; } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index c71dd2ce65..6fcc70e5f2 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -213,6 +213,7 @@ namespace osu.Game.Screens.Play public void OnReleased(GlobalAction action) { } + private void updateRetryCount() { // "You've retried 1,065 times in this session" From 4b1b5a88fe0dbef52f1297a06f584468fba3ad54 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 6 Jul 2021 14:39:53 +0200 Subject: [PATCH 1354/1621] Add null check to supress quality errors --- .../SelectionCycleFillFlowContainer.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 3dbe1f8f47..4f20c0039b 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -18,6 +18,8 @@ namespace osu.Game.Graphics.Containers { private int? selectedIndex; + public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex.Value] : null; + private void setSelected(int? value) { if (selectedIndex == value) @@ -51,6 +53,7 @@ namespace osu.Game.Graphics.Containers } public void Deselect() => setSelected(null); + public void Select(T item) { var newIndex = IndexOf(item); @@ -61,19 +64,20 @@ namespace osu.Game.Graphics.Containers setSelected(IndexOf(item)); } - public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex.Value] : null; - private readonly Dictionary> handlerMap = new Dictionary>(); public override void Add(T drawable) { - // This event is used to update selection state when modified within the drawable itself. - // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container - handlerMap[drawable] = state => selectionChanged(drawable, state); - - drawable.StateChanged += handlerMap[drawable]; - base.Add(drawable); + + if (drawable != null) + { + // This event is used to update selection state when modified within the drawable itself. + // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container + handlerMap[drawable] = state => selectionChanged(drawable, state); + + drawable.StateChanged += handlerMap[drawable]; + } } public override bool Remove(T drawable) @@ -81,7 +85,7 @@ namespace osu.Game.Graphics.Containers if (!base.Remove(drawable)) return false; - if (handlerMap.TryGetValue(drawable, out var action)) + if (drawable != null && handlerMap.TryGetValue(drawable, out var action)) { drawable.StateChanged -= action; handlerMap.Remove(drawable); From 4451598bcfe8f5b62b3f19c0b108290bfd448d09 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 6 Jul 2021 15:17:19 +0200 Subject: [PATCH 1355/1621] Fix remaining quality complaints --- osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs | 2 +- osu.Game/Graphics/UserInterface/DialogButton.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index d6a50fc346..ed40a83831 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First())); AddStep("Hide overlay", () => failOverlay.Hide()); - AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.State == SelectionState.Selected)); + AddAssert("Overlay state is reset", () => failOverlay.Buttons.All(b => b.State == SelectionState.NotSelected)); } /// diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index f9fbec2b48..2d75dad828 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -3,7 +3,6 @@ using System; using osu.Framework; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; From 255f7b7b532a25276272331a4e224c9389a6931a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 6 Jul 2021 16:32:02 +0300 Subject: [PATCH 1356/1621] Add failing test scene --- .../Skins/TestSceneSkinProvidingContainer.cs | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs diff --git a/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs new file mode 100644 index 0000000000..cfc4ccd208 --- /dev/null +++ b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Skins +{ + public class TestSceneSkinProvidingContainer : OsuTestScene + { + /// + /// Ensures that the first inserted skin after resetting (via source change) + /// is always prioritised over others when providing the same resource. + /// + [Test] + public void TestPriorityPreservation() + { + TestSkinProvidingContainer provider = null; + TestSkin mostPrioritisedSource = null; + + AddStep("setup sources", () => + { + var sources = new List(); + for (int i = 0; i < 10; i++) + sources.Add(new TestSkin()); + + mostPrioritisedSource = sources.First(); + + Child = provider = new TestSkinProvidingContainer(sources); + }); + + AddAssert("texture provided by expected skin", () => + { + return provider.FindProvider(s => s.GetTexture(TestSkin.TEXTURE_NAME) != null) == mostPrioritisedSource; + }); + + AddStep("trigger source change", () => provider.TriggerSourceChanged()); + + AddAssert("texture still provided by expected skin", () => + { + return provider.FindProvider(s => s.GetTexture(TestSkin.TEXTURE_NAME) != null) == mostPrioritisedSource; + }); + } + + private class TestSkinProvidingContainer : SkinProvidingContainer + { + private readonly IEnumerable sources; + + public TestSkinProvidingContainer(IEnumerable sources) + { + this.sources = sources; + } + + public new void TriggerSourceChanged() => base.TriggerSourceChanged(); + + protected override void OnSourceChanged() + { + ResetSources(); + sources.ForEach(AddSource); + } + } + + private class TestSkin : ISkin + { + public const string TEXTURE_NAME = "virtual-texture"; + + public Drawable GetDrawableComponent(ISkinComponent component) => throw new System.NotImplementedException(); + + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) + { + if (componentName == TEXTURE_NAME) + return Texture.WhitePixel; + + return null; + } + + public ISample GetSample(ISampleInfo sampleInfo) => throw new System.NotImplementedException(); + + public IBindable GetConfig(TLookup lookup) => throw new System.NotImplementedException(); + } + } +} From 523546d8b636239a6e6213c0104439eefc9ecbb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Jul 2021 22:51:56 +0900 Subject: [PATCH 1357/1621] Use List to guarantee lookup order --- osu.Game/Skinning/SkinProvidingContainer.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index d2f38b58a4..a628e97578 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Skinning /// /// A dictionary mapping each source to a wrapper which handles lookup allowances. /// - private readonly Dictionary skinSources = new Dictionary(); + private readonly List<(ISkin skin, DisableableSkinSource wrapped)> skinSources = new List<(ISkin, DisableableSkinSource)>(); /// /// Constructs a new initialised with a single skin source. @@ -70,7 +70,7 @@ namespace osu.Game.Skinning /// The skin to add. protected void AddSource(ISkin skin) { - skinSources.Add(skin, new DisableableSkinSource(skin, this)); + skinSources.Add((skin, new DisableableSkinSource(skin, this))); if (skin is ISkinSource source) source.SourceChanged += TriggerSourceChanged; @@ -82,7 +82,7 @@ namespace osu.Game.Skinning /// The skin to remove. protected void RemoveSource(ISkin skin) { - if (!skinSources.Remove(skin)) + if (skinSources.RemoveAll(s => s.skin == skin) == 0) return; if (skin is ISkinSource source) @@ -116,8 +116,8 @@ namespace osu.Game.Skinning { get { - foreach (var skin in skinSources.Keys) - yield return skin; + foreach (var i in skinSources) + yield return i.skin; if (AllowFallingBackToParent && ParentSource != null) { @@ -226,8 +226,11 @@ namespace osu.Game.Skinning if (ParentSource != null) ParentSource.SourceChanged -= TriggerSourceChanged; - foreach (var source in skinSources.Keys.OfType()) - source.SourceChanged -= TriggerSourceChanged; + foreach (var i in skinSources) + { + if (i.skin is ISkinSource source) + source.SourceChanged -= TriggerSourceChanged; + } } private class DisableableSkinSource : ISkin From f45418dde7b50ee63bd7c12d686cde40022c0617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 6 Jul 2021 00:15:17 +0200 Subject: [PATCH 1358/1621] Replace game-side directory/file selector with framework extensions --- .../Settings/TestSceneDirectorySelector.cs | 2 +- .../Visual/Settings/TestSceneFileSelector.cs | 4 +- .../Screens/Setup/StablePathSelectScreen.cs | 4 +- .../UserInterfaceV2/DirectorySelector.cs | 297 ------------------ .../Graphics/UserInterfaceV2/FileSelector.cs | 94 ------ .../UserInterfaceV2/OsuDirectorySelector.cs | 140 +++++++++ .../UserInterfaceV2/OsuFileSelector.cs | 90 ++++++ .../Maintenance/DirectorySelectScreen.cs | 4 +- .../Edit/Setup/FileChooserLabelledTextBox.cs | 4 +- osu.Game/Screens/Import/FileImportScreen.cs | 4 +- 10 files changed, 241 insertions(+), 402 deletions(-) delete mode 100644 osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs delete mode 100644 osu.Game/Graphics/UserInterfaceV2/FileSelector.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs b/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs index 082d85603e..227bce0c60 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Settings [BackgroundDependencyLoader] private void load() { - Add(new DirectorySelector { RelativeSizeAxes = Axes.Both }); + Add(new OsuDirectorySelector { RelativeSizeAxes = Axes.Both }); } } } diff --git a/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs index 311e4c3362..84a0fc6e4c 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs @@ -12,13 +12,13 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestAllFiles() { - AddStep("create", () => Child = new FileSelector { RelativeSizeAxes = Axes.Both }); + AddStep("create", () => Child = new OsuFileSelector { RelativeSizeAxes = Axes.Both }); } [Test] public void TestJpgFilesOnly() { - AddStep("create", () => Child = new FileSelector(validFileExtensions: new[] { ".jpg" }) { RelativeSizeAxes = Axes.Both }); + AddStep("create", () => Child = new OsuFileSelector(validFileExtensions: new[] { ".jpg" }) { RelativeSizeAxes = Axes.Both }); } } } diff --git a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs index 03f79b644f..3752d9d3be 100644 --- a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Screens.Setup [Resolved] private MatchIPCInfo ipc { get; set; } - private DirectorySelector directorySelector; + private OsuDirectorySelector directorySelector; private DialogOverlay overlay; [BackgroundDependencyLoader(true)] @@ -79,7 +79,7 @@ namespace osu.Game.Tournament.Screens.Setup }, new Drawable[] { - directorySelector = new DirectorySelector(initialPath) + directorySelector = new OsuDirectorySelector(initialPath) { RelativeSizeAxes = Axes.Both, } diff --git a/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs b/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs deleted file mode 100644 index a1cd074619..0000000000 --- a/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs +++ /dev/null @@ -1,297 +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 System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Framework.Platform; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Graphics.UserInterfaceV2 -{ - public class DirectorySelector : CompositeDrawable - { - private FillFlowContainer directoryFlow; - - [Resolved] - private GameHost host { get; set; } - - [Cached] - public readonly Bindable CurrentPath = new Bindable(); - - public DirectorySelector(string initialPath = null) - { - CurrentPath.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); - } - - [BackgroundDependencyLoader] - private void load() - { - Padding = new MarginPadding(10); - - InternalChild = new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 50), - new Dimension(), - }, - Content = new[] - { - new Drawable[] - { - new CurrentDirectoryDisplay - { - RelativeSizeAxes = Axes.Both, - }, - }, - new Drawable[] - { - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = directoryFlow = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(2), - } - } - } - } - }; - - CurrentPath.BindValueChanged(updateDisplay, true); - } - - private void updateDisplay(ValueChangedEvent directory) - { - directoryFlow.Clear(); - - try - { - if (directory.NewValue == null) - { - var drives = DriveInfo.GetDrives(); - - foreach (var drive in drives) - directoryFlow.Add(new DirectoryPiece(drive.RootDirectory)); - } - else - { - directoryFlow.Add(new ParentDirectoryPiece(CurrentPath.Value.Parent)); - - directoryFlow.AddRange(GetEntriesForPath(CurrentPath.Value)); - } - } - catch (Exception) - { - CurrentPath.Value = directory.OldValue; - this.FlashColour(Color4.Red, 300); - } - } - - protected virtual IEnumerable GetEntriesForPath(DirectoryInfo path) - { - foreach (var dir in path.GetDirectories().OrderBy(d => d.Name)) - { - if ((dir.Attributes & FileAttributes.Hidden) == 0) - yield return new DirectoryPiece(dir); - } - } - - private class CurrentDirectoryDisplay : CompositeDrawable - { - [Resolved] - private Bindable currentDirectory { get; set; } - - private FillFlowContainer flow; - - [BackgroundDependencyLoader] - private void load() - { - InternalChildren = new Drawable[] - { - flow = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Spacing = new Vector2(5), - Height = DisplayPiece.HEIGHT, - Direction = FillDirection.Horizontal, - }, - }; - - currentDirectory.BindValueChanged(updateDisplay, true); - } - - private void updateDisplay(ValueChangedEvent dir) - { - flow.Clear(); - - List pathPieces = new List(); - - DirectoryInfo ptr = dir.NewValue; - - while (ptr != null) - { - pathPieces.Insert(0, new CurrentDisplayPiece(ptr)); - ptr = ptr.Parent; - } - - flow.ChildrenEnumerable = new Drawable[] - { - new OsuSpriteText { Text = "Current Directory: ", Font = OsuFont.Default.With(size: DisplayPiece.HEIGHT), }, - new ComputerPiece(), - }.Concat(pathPieces); - } - - private class ComputerPiece : CurrentDisplayPiece - { - protected override IconUsage? Icon => null; - - public ComputerPiece() - : base(null, "Computer") - { - } - } - - private class CurrentDisplayPiece : DirectoryPiece - { - public CurrentDisplayPiece(DirectoryInfo directory, string displayName = null) - : base(directory, displayName) - { - } - - [BackgroundDependencyLoader] - private void load() - { - Flow.Add(new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.ChevronRight, - Size = new Vector2(FONT_SIZE / 2) - }); - } - - protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? base.Icon : null; - } - } - - private class ParentDirectoryPiece : DirectoryPiece - { - protected override IconUsage? Icon => FontAwesome.Solid.Folder; - - public ParentDirectoryPiece(DirectoryInfo directory) - : base(directory, "..") - { - } - } - - protected class DirectoryPiece : DisplayPiece - { - protected readonly DirectoryInfo Directory; - - [Resolved] - private Bindable currentDirectory { get; set; } - - public DirectoryPiece(DirectoryInfo directory, string displayName = null) - : base(displayName) - { - Directory = directory; - } - - protected override bool OnClick(ClickEvent e) - { - currentDirectory.Value = Directory; - return true; - } - - protected override string FallbackName => Directory.Name; - - protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) - ? FontAwesome.Solid.Database - : FontAwesome.Regular.Folder; - } - - protected abstract class DisplayPiece : CompositeDrawable - { - public const float HEIGHT = 20; - - protected const float FONT_SIZE = 16; - - private readonly string displayName; - - protected FillFlowContainer Flow; - - protected DisplayPiece(string displayName = null) - { - this.displayName = displayName; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AutoSizeAxes = Axes.Both; - - Masking = true; - CornerRadius = 5; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = colours.GreySeafoamDarker, - RelativeSizeAxes = Axes.Both, - }, - Flow = new FillFlowContainer - { - AutoSizeAxes = Axes.X, - Height = 20, - Margin = new MarginPadding { Vertical = 2, Horizontal = 5 }, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - } - }; - - if (Icon.HasValue) - { - Flow.Add(new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = Icon.Value, - Size = new Vector2(FONT_SIZE) - }); - } - - Flow.Add(new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = displayName ?? FallbackName, - Font = OsuFont.Default.With(size: FONT_SIZE) - }); - } - - protected abstract string FallbackName { get; } - - protected abstract IconUsage? Icon { get; } - } - } -} diff --git a/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs deleted file mode 100644 index e10b8f7033..0000000000 --- a/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs +++ /dev/null @@ -1,94 +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 System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; - -namespace osu.Game.Graphics.UserInterfaceV2 -{ - public class FileSelector : DirectorySelector - { - private readonly string[] validFileExtensions; - - [Cached] - public readonly Bindable CurrentFile = new Bindable(); - - public FileSelector(string initialPath = null, string[] validFileExtensions = null) - : base(initialPath) - { - this.validFileExtensions = validFileExtensions ?? Array.Empty(); - } - - protected override IEnumerable GetEntriesForPath(DirectoryInfo path) - { - foreach (var dir in base.GetEntriesForPath(path)) - yield return dir; - - IEnumerable files = path.GetFiles(); - - if (validFileExtensions.Length > 0) - files = files.Where(f => validFileExtensions.Contains(f.Extension)); - - foreach (var file in files.OrderBy(d => d.Name)) - { - if ((file.Attributes & FileAttributes.Hidden) == 0) - yield return new FilePiece(file); - } - } - - protected class FilePiece : DisplayPiece - { - private readonly FileInfo file; - - [Resolved] - private Bindable currentFile { get; set; } - - public FilePiece(FileInfo file) - { - this.file = file; - } - - protected override bool OnClick(ClickEvent e) - { - currentFile.Value = file; - return true; - } - - protected override string FallbackName => file.Name; - - protected override IconUsage? Icon - { - get - { - switch (file.Extension) - { - case ".ogg": - case ".mp3": - case ".wav": - return FontAwesome.Regular.FileAudio; - - case ".jpg": - case ".jpeg": - case ".png": - return FontAwesome.Regular.FileImage; - - case ".mp4": - case ".avi": - case ".mov": - case ".flv": - return FontAwesome.Regular.FileVideo; - - default: - return FontAwesome.Regular.File; - } - } - } - } - } -} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs new file mode 100644 index 0000000000..9bc66f6c9f --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs @@ -0,0 +1,140 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class OsuDirectorySelector : DirectorySelector + { + public const float ITEM_HEIGHT = 20; + + public OsuDirectorySelector(string initialPath = null) + : base(initialPath) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Padding = new MarginPadding(10); + } + + protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); + + protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay(); + + protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory); + + protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName); + + protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300); + + internal class OsuDirectorySelectorBreadcrumbDisplay : DirectorySelectorBreadcrumbDisplay + { + protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer(); + protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName); + + [BackgroundDependencyLoader] + private void load() + { + Height = 50; + } + + private class OsuBreadcrumbDisplayComputer : OsuBreadcrumbDisplayDirectory + { + protected override IconUsage? Icon => null; + + public OsuBreadcrumbDisplayComputer() + : base(null, "Computer") + { + } + } + + private class OsuBreadcrumbDisplayDirectory : OsuDirectorySelectorDirectory + { + public OsuBreadcrumbDisplayDirectory(DirectoryInfo directory, string displayName = null) + : base(directory, displayName) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Flow.Add(new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(FONT_SIZE / 2) + }); + } + + protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? base.Icon : null; + } + } + + internal class OsuDirectorySelectorParentDirectory : OsuDirectorySelectorDirectory + { + protected override IconUsage? Icon => FontAwesome.Solid.Folder; + + public OsuDirectorySelectorParentDirectory(DirectoryInfo directory) + : base(directory, "..") + { + } + } + + internal class OsuDirectorySelectorDirectory : DirectorySelectorDirectory + { + public OsuDirectorySelectorDirectory(DirectoryInfo directory, string displayName = null) + : base(directory, displayName) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Flow.AutoSizeAxes = Axes.X; + Flow.Height = ITEM_HEIGHT; + + AddInternal(new OsuDirectorySelectorItemBackground + { + Depth = 1 + }); + } + + protected override SpriteText CreateSpriteText() => new OsuSpriteText(); + + protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) + ? FontAwesome.Solid.Database + : FontAwesome.Regular.Folder; + } + + internal class OsuDirectorySelectorItemBackground : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + + Masking = true; + CornerRadius = 5; + + InternalChild = new Box + { + Colour = colours.GreySeafoamDarker, + RelativeSizeAxes = Axes.Both, + }; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs new file mode 100644 index 0000000000..c50178100e --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class OsuFileSelector : FileSelector + { + public OsuFileSelector(string initialPath = null, string[] validFileExtensions = null) + : base(initialPath, validFileExtensions) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Padding = new MarginPadding(10); + } + + protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); + + protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelector.OsuDirectorySelectorBreadcrumbDisplay(); + + protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelector.OsuDirectorySelectorParentDirectory(directory); + + protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelector.OsuDirectorySelectorDirectory(directory, displayName); + + protected override DirectoryListingFile CreateFileItem(FileInfo file) => new OsuDirectoryListingFile(file); + + protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300); + + protected class OsuDirectoryListingFile : DirectoryListingFile + { + public OsuDirectoryListingFile(FileInfo file) + : base(file) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Flow.AutoSizeAxes = Axes.X; + Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; + + AddInternal(new OsuDirectorySelector.OsuDirectorySelectorItemBackground + { + Depth = 1 + }); + } + + protected override IconUsage? Icon + { + get + { + switch (File.Extension) + { + case @".ogg": + case @".mp3": + case @".wav": + return FontAwesome.Regular.FileAudio; + + case @".jpg": + case @".jpeg": + case @".png": + return FontAwesome.Regular.FileImage; + + case @".mp4": + case @".avi": + case @".mov": + case @".flv": + return FontAwesome.Regular.FileVideo; + + default: + return FontAwesome.Regular.File; + } + } + } + + protected override SpriteText CreateSpriteText() => new OsuSpriteText(); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index 349a112477..5392ba5d93 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { private TriangleButton selectionButton; - private DirectorySelector directorySelector; + private OsuDirectorySelector directorySelector; /// /// Text to display in the header to inform the user of what they are selecting. @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }, new Drawable[] { - directorySelector = new DirectorySelector + directorySelector = new OsuDirectorySelector { RelativeSizeAxes = Axes.Both, } diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs index a33a70af65..69c27702f8 100644 --- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs @@ -56,9 +56,9 @@ namespace osu.Game.Screens.Edit.Setup public void DisplayFileChooser() { - FileSelector fileSelector; + OsuFileSelector fileSelector; - Target.Child = fileSelector = new FileSelector(currentFile.Value?.DirectoryName, handledExtensions) + Target.Child = fileSelector = new OsuFileSelector(currentFile.Value?.DirectoryName, handledExtensions) { RelativeSizeAxes = Axes.X, Height = 400, diff --git a/osu.Game/Screens/Import/FileImportScreen.cs b/osu.Game/Screens/Import/FileImportScreen.cs index ee8ef6926d..7e1d55b3e2 100644 --- a/osu.Game/Screens/Import/FileImportScreen.cs +++ b/osu.Game/Screens/Import/FileImportScreen.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Import { public override bool HideOverlaysOnEnter => true; - private FileSelector fileSelector; + private OsuFileSelector fileSelector; private Container contentContainer; private TextFlowContainer currentFileText; @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Import Colour = colours.GreySeafoamDark, RelativeSizeAxes = Axes.Both, }, - fileSelector = new FileSelector(validFileExtensions: game.HandledExtensions.ToArray()) + fileSelector = new OsuFileSelector(validFileExtensions: game.HandledExtensions.ToArray()) { RelativeSizeAxes = Axes.Both, Width = 0.65f From e94e283ee4083c82652302f04817346b39b085d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 6 Jul 2021 00:20:58 +0200 Subject: [PATCH 1359/1621] Move shared inner classes to separate files --- .../UserInterfaceV2/OsuDirectorySelector.cs | 102 ------------------ .../OsuDirectorySelectorBreadcrumbDisplay.cs | 64 +++++++++++ .../OsuDirectorySelectorDirectory.cs | 58 ++++++++++ .../OsuDirectorySelectorParentDirectory.cs | 18 ++++ .../UserInterfaceV2/OsuFileSelector.cs | 8 +- 5 files changed, 144 insertions(+), 106 deletions(-) create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs index 9bc66f6c9f..1ce4d97fdf 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs @@ -5,12 +5,8 @@ using System.IO; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osuTK; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -38,103 +34,5 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName); protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300); - - internal class OsuDirectorySelectorBreadcrumbDisplay : DirectorySelectorBreadcrumbDisplay - { - protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer(); - protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName); - - [BackgroundDependencyLoader] - private void load() - { - Height = 50; - } - - private class OsuBreadcrumbDisplayComputer : OsuBreadcrumbDisplayDirectory - { - protected override IconUsage? Icon => null; - - public OsuBreadcrumbDisplayComputer() - : base(null, "Computer") - { - } - } - - private class OsuBreadcrumbDisplayDirectory : OsuDirectorySelectorDirectory - { - public OsuBreadcrumbDisplayDirectory(DirectoryInfo directory, string displayName = null) - : base(directory, displayName) - { - } - - [BackgroundDependencyLoader] - private void load() - { - Flow.Add(new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.ChevronRight, - Size = new Vector2(FONT_SIZE / 2) - }); - } - - protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? base.Icon : null; - } - } - - internal class OsuDirectorySelectorParentDirectory : OsuDirectorySelectorDirectory - { - protected override IconUsage? Icon => FontAwesome.Solid.Folder; - - public OsuDirectorySelectorParentDirectory(DirectoryInfo directory) - : base(directory, "..") - { - } - } - - internal class OsuDirectorySelectorDirectory : DirectorySelectorDirectory - { - public OsuDirectorySelectorDirectory(DirectoryInfo directory, string displayName = null) - : base(directory, displayName) - { - } - - [BackgroundDependencyLoader] - private void load() - { - Flow.AutoSizeAxes = Axes.X; - Flow.Height = ITEM_HEIGHT; - - AddInternal(new OsuDirectorySelectorItemBackground - { - Depth = 1 - }); - } - - protected override SpriteText CreateSpriteText() => new OsuSpriteText(); - - protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) - ? FontAwesome.Solid.Database - : FontAwesome.Regular.Folder; - } - - internal class OsuDirectorySelectorItemBackground : CompositeDrawable - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - RelativeSizeAxes = Axes.Both; - - Masking = true; - CornerRadius = 5; - - InternalChild = new Box - { - Colour = colours.GreySeafoamDarker, - RelativeSizeAxes = Axes.Both, - }; - } - } } } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs new file mode 100644 index 0000000000..cb5ff242a1 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.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 System.IO; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + internal class OsuDirectorySelectorBreadcrumbDisplay : DirectorySelectorBreadcrumbDisplay + { + protected override Drawable CreateCaption() => new OsuSpriteText + { + Text = "Current Directory: ", + Font = OsuFont.Default.With(size: OsuDirectorySelector.ITEM_HEIGHT), + }; + + protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer(); + + protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName); + + [BackgroundDependencyLoader] + private void load() + { + Height = 50; + } + + private class OsuBreadcrumbDisplayComputer : OsuBreadcrumbDisplayDirectory + { + protected override IconUsage? Icon => null; + + public OsuBreadcrumbDisplayComputer() + : base(null, "Computer") + { + } + } + + private class OsuBreadcrumbDisplayDirectory : OsuDirectorySelectorDirectory + { + public OsuBreadcrumbDisplayDirectory(DirectoryInfo directory, string displayName = null) + : base(directory, displayName) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Flow.Add(new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(FONT_SIZE / 2) + }); + } + + protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? base.Icon : null; + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs new file mode 100644 index 0000000000..8a420cdcfb --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + internal class OsuDirectorySelectorDirectory : DirectorySelectorDirectory + { + public OsuDirectorySelectorDirectory(DirectoryInfo directory, string displayName = null) + : base(directory, displayName) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Flow.AutoSizeAxes = Axes.X; + Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; + + AddInternal(new Background + { + Depth = 1 + }); + } + + protected override SpriteText CreateSpriteText() => new OsuSpriteText(); + + protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) + ? FontAwesome.Solid.Database + : FontAwesome.Regular.Folder; + + internal class Background : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + + Masking = true; + CornerRadius = 5; + + InternalChild = new Box + { + Colour = colours.GreySeafoamDarker, + RelativeSizeAxes = Axes.Both, + }; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs new file mode 100644 index 0000000000..481d811adb --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.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.IO; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + internal class OsuDirectorySelectorParentDirectory : OsuDirectorySelectorDirectory + { + protected override IconUsage? Icon => FontAwesome.Solid.Folder; + + public OsuDirectorySelectorParentDirectory(DirectoryInfo directory) + : base(directory, "..") + { + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs index c50178100e..b9fb642cbe 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs @@ -27,11 +27,11 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); - protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelector.OsuDirectorySelectorBreadcrumbDisplay(); + protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay(); - protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelector.OsuDirectorySelectorParentDirectory(directory); + protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory); - protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelector.OsuDirectorySelectorDirectory(directory, displayName); + protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName); protected override DirectoryListingFile CreateFileItem(FileInfo file) => new OsuDirectoryListingFile(file); @@ -50,7 +50,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Flow.AutoSizeAxes = Axes.X; Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; - AddInternal(new OsuDirectorySelector.OsuDirectorySelectorItemBackground + AddInternal(new OsuDirectorySelectorDirectory.Background { Depth = 1 }); From ddb1da5a6611b11769675f7558343c2b441b0e8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 13:48:35 +0900 Subject: [PATCH 1360/1621] Tidy up class (although it's not in a good state logically) --- .../SelectionCycleFillFlowContainer.cs | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 4f20c0039b..656f489772 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,25 +17,11 @@ namespace osu.Game.Graphics.Containers /// public class SelectionCycleFillFlowContainer : FillFlowContainer where T : Drawable, IStateful { - private int? selectedIndex; - public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex.Value] : null; - private void setSelected(int? value) - { - if (selectedIndex == value) - return; + private int? selectedIndex; - // Deselect the previously-selected button - if (selectedIndex.HasValue) - this[selectedIndex.Value].State = SelectionState.NotSelected; - - selectedIndex = value; - - // Select the newly-selected button - if (selectedIndex.HasValue) - this[selectedIndex.Value].State = SelectionState.Selected; - } + private readonly Dictionary> handlerMap = new Dictionary>(); public void SelectNext() { @@ -64,20 +51,17 @@ namespace osu.Game.Graphics.Containers setSelected(IndexOf(item)); } - private readonly Dictionary> handlerMap = new Dictionary>(); - public override void Add(T drawable) { base.Add(drawable); - if (drawable != null) - { - // This event is used to update selection state when modified within the drawable itself. - // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container - handlerMap[drawable] = state => selectionChanged(drawable, state); + Debug.Assert(drawable != null); - drawable.StateChanged += handlerMap[drawable]; - } + // This event is used to update selection state when modified within the drawable itself. + // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container + handlerMap[drawable] = state => selectionChanged(drawable, state); + + drawable.StateChanged += handlerMap[drawable]; } public override bool Remove(T drawable) @@ -85,7 +69,9 @@ namespace osu.Game.Graphics.Containers if (!base.Remove(drawable)) return false; - if (drawable != null && handlerMap.TryGetValue(drawable, out var action)) + Debug.Assert(drawable != null); + + if (handlerMap.TryGetValue(drawable, out var action)) { drawable.StateChanged -= action; handlerMap.Remove(drawable); @@ -94,6 +80,22 @@ namespace osu.Game.Graphics.Containers return true; } + private void setSelected(int? value) + { + if (selectedIndex == value) + return; + + // Deselect the previously-selected button + if (selectedIndex.HasValue) + this[selectedIndex.Value].State = SelectionState.NotSelected; + + selectedIndex = value; + + // Select the newly-selected button + if (selectedIndex.HasValue) + this[selectedIndex.Value].State = SelectionState.Selected; + } + private void selectionChanged(T drawable, SelectionState state) { if (state == SelectionState.NotSelected) From eb8b14a931cffd9fb99858aecfc06ffaaef30ee8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 13:51:51 +0900 Subject: [PATCH 1361/1621] Reorder methods to make more sense --- osu.Game/Skinning/SkinProvidingContainer.cs | 84 ++++++++++----------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index a628e97578..f386900f64 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -64,38 +64,19 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } - /// - /// Add a new skin to this provider. Will be added to the end of the lookup order precedence. - /// - /// The skin to add. - protected void AddSource(ISkin skin) + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - skinSources.Add((skin, new DisableableSkinSource(skin, this))); + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - if (skin is ISkinSource source) - source.SourceChanged += TriggerSourceChanged; - } + ParentSource = dependencies.Get(); + if (ParentSource != null) + ParentSource.SourceChanged += TriggerSourceChanged; - /// - /// Remove a skin from this provider. - /// - /// The skin to remove. - protected void RemoveSource(ISkin skin) - { - if (skinSources.RemoveAll(s => s.skin == skin) == 0) - return; + dependencies.CacheAs(this); - if (skin is ISkinSource source) - source.SourceChanged -= TriggerSourceChanged; - } + TriggerSourceChanged(); - /// - /// Clears all skin sources. - /// - protected void ResetSources() - { - foreach (var skin in AllSources.ToArray()) - RemoveSource(skin); + return dependencies; } public ISkin FindProvider(Func lookupFunction) @@ -187,27 +168,46 @@ namespace osu.Game.Skinning return ParentSource?.GetConfig(lookup); } + /// + /// Add a new skin to this provider. Will be added to the end of the lookup order precedence. + /// + /// The skin to add. + protected void AddSource(ISkin skin) + { + skinSources.Add((skin, new DisableableSkinSource(skin, this))); + + if (skin is ISkinSource source) + source.SourceChanged += TriggerSourceChanged; + } + + /// + /// Remove a skin from this provider. + /// + /// The skin to remove. + protected void RemoveSource(ISkin skin) + { + if (skinSources.RemoveAll(s => s.skin == skin) == 0) + return; + + if (skin is ISkinSource source) + source.SourceChanged -= TriggerSourceChanged; + } + + /// + /// Clears all skin sources. + /// + protected void ResetSources() + { + foreach (var skin in AllSources.ToArray()) + RemoveSource(skin); + } + /// /// Invoked when any source has changed (either or a source registered via ). /// This is also invoked once initially during to ensure sources are ready for children consumption. /// protected virtual void OnSourceChanged() { } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - ParentSource = dependencies.Get(); - if (ParentSource != null) - ParentSource.SourceChanged += TriggerSourceChanged; - - dependencies.CacheAs(this); - - TriggerSourceChanged(); - - return dependencies; - } - protected void TriggerSourceChanged() { // Expose to implementations, giving them a chance to react before notifying external consumers. From 35d4b12a4f7bb0bb42b388d6c70cdb2c3d58c4b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 13:52:52 +0900 Subject: [PATCH 1362/1621] Remove single local usage of `AllSources` --- osu.Game/Skinning/SkinProvidingContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index f386900f64..d8f931da5e 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -198,8 +198,8 @@ namespace osu.Game.Skinning /// protected void ResetSources() { - foreach (var skin in AllSources.ToArray()) - RemoveSource(skin); + foreach (var i in skinSources) + RemoveSource(i.skin); } /// From ca791c2afa550c44b85de3380fa6dfaf6e41f4c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 13:53:00 +0900 Subject: [PATCH 1363/1621] Remove unused using statement --- osu.Game/Skinning/SkinProvidingContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index d8f931da5e..d1a0187f7b 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; From c18b8ca86c5095fcec39eae075eec175f6648eff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 14:08:29 +0900 Subject: [PATCH 1364/1621] Add missing `ToArray()` call --- osu.Game/Skinning/SkinProvidingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index d1a0187f7b..7c26fdaf03 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -197,7 +197,7 @@ namespace osu.Game.Skinning /// protected void ResetSources() { - foreach (var i in skinSources) + foreach (var i in skinSources.ToArray()) RemoveSource(i.skin); } From b08fece2d4126ae061e5f5d68fba259015450443 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 14:30:47 +0900 Subject: [PATCH 1365/1621] 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 cfc28ffb21..9280eaf97c 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 646d21dfee..8a3c69e40c 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 f36f7881cd..2eea646c61 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 35672f372a11fd69aa9e3ee67af5b7296131f32e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 14:58:01 +0900 Subject: [PATCH 1366/1621] Shorten test beatmap to avoid timeouts in score submission test --- .../Gameplay/TestScenePlayerScoreSubmission.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index d9c0544d3c..c3a46ec4ac 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -4,14 +4,18 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; +using osu.Game.Tests.Beatmaps; +using osuTK; namespace osu.Game.Tests.Visual.Gameplay { @@ -25,6 +29,15 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool HasCustomSteps => true; + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = (TestBeatmap)base.CreateBeatmap(ruleset); + + beatmap.HitObjects = beatmap.HitObjects.Take(10).ToList(); + + return beatmap; + } + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); [Test] From 28adb43a4a6151cc669d8512830e472cf51dc369 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 7 Jul 2021 09:26:17 +0300 Subject: [PATCH 1367/1621] Add detailed explaination for the reason of using old binding method --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 0790929ab2..44adb70108 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -23,6 +23,12 @@ namespace osu.Game.Overlays // this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button. public override bool AcceptsFocus => true; + // this is intentionally not using BindableWithCurrent as this needs a BindableNumber instance for an accurate IsDefault value. + // + // this also cannot use IBindableWithCurrent.Create() due to BindableNumberWithCurrent + // directly casting given bindables to BindableNumber, which is not necessarily the case. + // + // therefore rely on the old method of taking each current bindable instance for now, until things are settled framework-side. private Bindable current; public Bindable Current From faf95c7161b168c734a7d64d42e88b28566180be Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Jul 2021 15:35:14 +0900 Subject: [PATCH 1368/1621] Remove unused usings --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index c3a46ec4ac..f9ccb10778 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -11,11 +11,9 @@ using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Tests.Beatmaps; -using osuTK; namespace osu.Game.Tests.Visual.Gameplay { From 115376c53825adb05cddd3af0d056163e04fc003 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 7 Jul 2021 16:10:24 +0900 Subject: [PATCH 1369/1621] Add playfield border to catch editor --- .../Edit/CatchHitObjectComposer.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index d9712bc8e9..d360274aa6 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -2,6 +2,8 @@ // 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.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; @@ -20,6 +22,16 @@ namespace osu.Game.Rulesets.Catch.Edit { } + [BackgroundDependencyLoader] + private void load() + { + LayerBelowRuleset.Add(new PlayfieldBorder + { + RelativeSizeAxes = Axes.Both, + PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners } + }); + } + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchEditorRuleset(ruleset, beatmap, mods); From 7d76fcf2b64766c14dce7c54c6af03ab0cdb4b37 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 7 Jul 2021 16:13:34 +0900 Subject: [PATCH 1370/1621] Fix hit object placement not receiving input when outside playfield The input area is vertical infinite, but horizontally restricted to the playfield due to `CatchPlayfield`'s `ReceivePositionalInputAt` override. --- .../Edit/Blueprints/CatchPlacementBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs index 69054e2c81..5a32d241ad 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs @@ -6,6 +6,7 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Catch.Edit.Blueprints { @@ -23,5 +24,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints : base(new THitObject()) { } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; } } From 09925dffef48d0e20232cbd38d260792a4420246 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 16:30:23 +0900 Subject: [PATCH 1371/1621] Add missing `HeadlessTest` flag on new test scene --- osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs index cfc4ccd208..ab47067411 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs @@ -10,12 +10,14 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Tests.Skins { + [HeadlessTest] public class TestSceneSkinProvidingContainer : OsuTestScene { /// From 8b1876bc2a3341e4151123f9e56e1e8eec6e63a9 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 7 Jul 2021 11:43:54 +0200 Subject: [PATCH 1372/1621] Disallow removing items from SelectionCycleFillFlowContainer --- .../SelectionCycleFillFlowContainer.cs | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 656f489772..cef903f63e 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using osu.Framework; using osu.Framework.Graphics; @@ -21,8 +20,6 @@ namespace osu.Game.Graphics.Containers private int? selectedIndex; - private readonly Dictionary> handlerMap = new Dictionary>(); - public void SelectNext() { if (!selectedIndex.HasValue || selectedIndex == Count - 1) @@ -57,28 +54,12 @@ namespace osu.Game.Graphics.Containers Debug.Assert(drawable != null); - // This event is used to update selection state when modified within the drawable itself. - // It is added to a dictionary so that we can unsubscribe if the drawable is removed from this container - handlerMap[drawable] = state => selectionChanged(drawable, state); - - drawable.StateChanged += handlerMap[drawable]; + drawable.StateChanged += state => selectionChanged(drawable, state); } public override bool Remove(T drawable) - { - if (!base.Remove(drawable)) - return false; + => throw new NotSupportedException($"Cannot remove drawables from {nameof(SelectionCycleFillFlowContainer)}"); - Debug.Assert(drawable != null); - - if (handlerMap.TryGetValue(drawable, out var action)) - { - drawable.StateChanged -= action; - handlerMap.Remove(drawable); - } - - return true; - } private void setSelected(int? value) { From f53f6690e3982fecc1b4b5dcdfea4ef808b0b80c Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 7 Jul 2021 12:01:47 +0200 Subject: [PATCH 1373/1621] Remove extra blank line --- osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index cef903f63e..90b2d20e4d 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -60,7 +60,6 @@ namespace osu.Game.Graphics.Containers public override bool Remove(T drawable) => throw new NotSupportedException($"Cannot remove drawables from {nameof(SelectionCycleFillFlowContainer)}"); - private void setSelected(int? value) { if (selectedIndex == value) From 4d7c7441016aff5dc5f8e2b6f7476716e234a36c Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 7 Jul 2021 12:59:31 +0200 Subject: [PATCH 1374/1621] Fix failing test --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 6fcc70e5f2..2608c93fa1 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -41,7 +42,7 @@ namespace osu.Game.Screens.Play /// /// Action that is invoked when is triggered. /// - protected virtual Action BackAction => () => InternalButtons.Selected?.Click(); + protected virtual Action BackAction => () => InternalButtons.Children.LastOrDefault()?.Click(); /// /// Action that is invoked when is triggered. From 83283a706eca6bc8c39d8b775ac8ce5d2a8db13e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 20:51:13 +0900 Subject: [PATCH 1375/1621] Add test scene --- .../UserInterface/TestSceneVolumeOverlay.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs new file mode 100644 index 0000000000..64708c4858 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.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.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Volume; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneVolumeOverlay : OsuTestScene + { + private VolumeOverlay volume; + + protected override void LoadComplete() + { + base.LoadComplete(); + + AddRange(new Drawable[] + { + volume = new VolumeOverlay(), + new VolumeControlReceptor + { + RelativeSizeAxes = Axes.Both, + ActionRequested = action => volume.Adjust(action), + ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise), + }, + }); + + AddStep("show controls", () => volume.Show()); + } + } +} From f1aa99e1033c8115259f4b9dcbc7c9ecf49932b0 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 7 Jul 2021 21:01:55 +0900 Subject: [PATCH 1376/1621] Fix catch selection blueprint not displayed after copy-pasted --- .../Edit/Blueprints/CatchSelectionBlueprint.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs index 298f9474b0..720d730858 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs @@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints public abstract class CatchSelectionBlueprint : HitObjectSelectionBlueprint where THitObject : CatchHitObject { + protected override bool AlwaysShowWhenSelected => true; + public override Vector2 ScreenSpaceSelectionPoint { get From cbe4114e90d8a16f960e6a3583139bbf7072ffc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 21:02:48 +0900 Subject: [PATCH 1377/1621] Adjust visuals and make base opacity 100% --- osu.Game/Overlays/Volume/VolumeMeter.cs | 75 ++++++++++++------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index a7c4fb6e7d..82ab45428a 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Volume private OsuSpriteText text; private BufferedContainer maxGlow; - private Container focusGlowContainer; + private Container selectedGlowContainer; private Sample sample; private double sampleLastPlaybackTime; @@ -59,6 +59,8 @@ namespace osu.Game.Overlays.Volume state = value; StateChanged?.Invoke(value); + + updateSelectedState(); } } @@ -94,28 +96,8 @@ namespace osu.Game.Overlays.Volume Size = new Vector2(circleSize), Children = new Drawable[] { - focusGlowContainer = new CircularContainer - { - Masking = true, - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = meterColour.Opacity(0.5f), - Radius = 5, - Hollow = true, - } - }, new BufferedContainer { - Alpha = 0.9f, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { @@ -187,6 +169,24 @@ namespace osu.Game.Overlays.Volume }, }, }, + selectedGlowContainer = new CircularContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = meterColour.Opacity(0.1f), + Radius = 10, + } + }, maxGlow = (text = new OsuSpriteText { Anchor = Anchor.Centre, @@ -211,7 +211,6 @@ namespace osu.Game.Overlays.Volume { new Box { - Alpha = 0.9f, RelativeSizeAxes = Axes.Both, Colour = backgroundColour, }, @@ -229,8 +228,6 @@ namespace osu.Game.Overlays.Volume Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true); bgProgress.Current.Value = 0.75f; - - StateChanged += stateChanged; } private int? displayVolumeInt; @@ -359,20 +356,6 @@ namespace osu.Game.Overlays.Volume { } - private void stateChanged(SelectionState newState) - { - if (newState == SelectionState.Selected) - { - this.ScaleTo(1.04f, transition_length, Easing.OutExpo); - focusGlowContainer.FadeIn(transition_length, Easing.OutExpo); - } - else - { - this.ScaleTo(1f, transition_length, Easing.OutExpo); - focusGlowContainer.FadeOut(transition_length, Easing.OutExpo); - } - } - public bool OnPressed(GlobalAction action) { if (!IsHovered) @@ -397,5 +380,21 @@ namespace osu.Game.Overlays.Volume public void OnReleased(GlobalAction action) { } + + private void updateSelectedState() + { + switch (state) + { + case SelectionState.Selected: + this.ScaleTo(1.04f, transition_length, Easing.OutExpo); + selectedGlowContainer.FadeIn(transition_length, Easing.OutExpo); + break; + + case SelectionState.NotSelected: + this.ScaleTo(1f, transition_length, Easing.OutExpo); + selectedGlowContainer.FadeOut(transition_length, Easing.OutExpo); + break; + } + } } } From 7d405f04fbff8d449864958e2ddb4b3c65511ce1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 21:17:31 +0900 Subject: [PATCH 1378/1621] Fix selected volume control not updating correctly on mouse move --- osu.Game/Overlays/Volume/VolumeMeter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 82ab45428a..eed6f5c2f3 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -64,6 +64,8 @@ namespace osu.Game.Overlays.Volume } } + private const float transition_length = 500; + public VolumeMeter(string name, float circleSize, Color4 meterColour) { this.circleSize = circleSize; @@ -344,12 +346,10 @@ namespace osu.Game.Overlays.Volume return true; } - private const float transition_length = 500; - - protected override bool OnHover(HoverEvent e) + protected override bool OnMouseMove(MouseMoveEvent e) { State = SelectionState.Selected; - return false; + return base.OnMouseMove(e); } protected override void OnHoverLost(HoverLostEvent e) From ddca132ab594c7c325e4f609656be2bc73352338 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Jul 2021 21:38:38 +0900 Subject: [PATCH 1379/1621] Add difficulty adjustment mod tests --- .../Mods/ModDifficultyAdjustTest.cs | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs diff --git a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs new file mode 100644 index 0000000000..fcbdcbe724 --- /dev/null +++ b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs @@ -0,0 +1,168 @@ +// 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 NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Tests.Mods +{ + [TestFixture] + [Ignore("Currently broken, pending fixes/reworking of ModDifficultyAdjust.")] + public class ModDifficultyAdjustTest + { + // Todo: This shouldn't exist, but is currently required for the mod to accept a new BeatmapDifficulty object... + private int currentId; + + private TestModDifficultyAdjust testMod; + + [SetUp] + public void Setup() + { + currentId = 0; + testMod = new TestModDifficultyAdjust(); + + // Todo: This shouldn't be a thing, but is currently required because this causes the mod to keep track of the bindables internally... + applyDifficulty(new BeatmapDifficulty + { + DrainRate = -1, + OverallDifficulty = -1 + }); + } + + [Test] + public void TestUnchangedSettingsFollowAppliedDifficulty() + { + var result = applyDifficulty(new BeatmapDifficulty + { + DrainRate = 10, + OverallDifficulty = 10 + }); + + Assert.That(result.DrainRate, Is.EqualTo(10)); + Assert.That(result.OverallDifficulty, Is.EqualTo(10)); + + result = applyDifficulty(new BeatmapDifficulty + { + DrainRate = 1, + OverallDifficulty = 1 + }); + + Assert.That(result.DrainRate, Is.EqualTo(1)); + Assert.That(result.OverallDifficulty, Is.EqualTo(1)); + } + + [Test] + public void TestChangedSettingsOverrideAppliedDifficulty() + { + testMod.OverallDifficulty.Value = 4; + + var result = applyDifficulty(new BeatmapDifficulty + { + DrainRate = 10, + OverallDifficulty = 10 + }); + + Assert.That(result.DrainRate, Is.EqualTo(10)); + Assert.That(result.OverallDifficulty, Is.EqualTo(4)); + + result = applyDifficulty(new BeatmapDifficulty + { + DrainRate = 1, + OverallDifficulty = 1 + }); + + Assert.That(result.DrainRate, Is.EqualTo(1)); + Assert.That(result.OverallDifficulty, Is.EqualTo(4)); + } + + [Test] + public void TestChangedSettingsRetainedWhenSameValueIsApplied() + { + testMod.OverallDifficulty.Value = 4; + + // Apply and de-apply the same value as the mod. + applyDifficulty(new BeatmapDifficulty { OverallDifficulty = 4 }); + var result = applyDifficulty(new BeatmapDifficulty { OverallDifficulty = 10 }); + + Assert.That(result.OverallDifficulty, Is.EqualTo(4)); + } + + [Test] + public void TestChangedSettingSerialisedWhenSameValueIsApplied() + { + applyDifficulty(new BeatmapDifficulty { OverallDifficulty = 4 }); + testMod.OverallDifficulty.Value = 4; + + var result = (TestModDifficultyAdjust)new APIMod(testMod).ToMod(new TestRuleset()); + + Assert.That(result.OverallDifficulty.Value, Is.EqualTo(4)); + } + + [Test] + public void TestChangedSettingsRevertedToDefault() + { + applyDifficulty(new BeatmapDifficulty + { + DrainRate = 10, + OverallDifficulty = 10 + }); + + testMod.OverallDifficulty.Value = 4; + testMod.ResetSettingsToDefaults(); + + Assert.That(testMod.DrainRate.Value, Is.EqualTo(10)); + Assert.That(testMod.OverallDifficulty.Value, Is.EqualTo(10)); + } + + /// + /// Applies a to the mod and returns a new + /// representing the result if the mod were applied to a fresh instance. + /// + private BeatmapDifficulty applyDifficulty(BeatmapDifficulty difficulty) + { + difficulty.ID = ++currentId; + testMod.ReadFromDifficulty(difficulty); + + var newDifficulty = new BeatmapDifficulty(); + testMod.ApplyToDifficulty(newDifficulty); + return newDifficulty; + } + + private class TestModDifficultyAdjust : ModDifficultyAdjust + { + } + + private class TestRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) + { + if (type == ModType.DifficultyIncrease) + yield return new TestModDifficultyAdjust(); + } + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) + { + throw new System.NotImplementedException(); + } + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) + { + throw new System.NotImplementedException(); + } + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) + { + throw new System.NotImplementedException(); + } + + public override string Description => string.Empty; + public override string ShortName => string.Empty; + } + } +} From 341cb09c6eb5399b3ba113cf92ef2c620e4dc43a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 12:06:52 +0900 Subject: [PATCH 1380/1621] Update terminology in README At some point we'll want to replace the link to the outdated blog post (or just remove it?) with the gantt or otherwise. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2213b42121..e95c12cfdc 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A free-to-win rhythm game. Rhythm is just a *click* away! -The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename *osu!lazer*. Pew pew. +The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the codename "*lazer*". As in sharper than cutting-edge. ## Status @@ -23,7 +23,7 @@ We are accepting bug reports (please report with as much detail as possible and - Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer). - You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management). -- Read peppy's [latest blog post](https://blog.ppy.sh/a-definitive-lazer-faq/) exploring where lazer is currently and the roadmap going forward. +- Read peppy's [latest blog post](https://blog.ppy.sh/a-definitive-lazer-faq/) exploring where the project is currently and the roadmap going forward. ## Running osu! From 663ffae42f9b8e97037e7f4c3ba06b8acb68d600 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 7 Jul 2021 21:02:11 +0900 Subject: [PATCH 1381/1621] Fix hit object selection blueprint potential null reference --- osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs index 56434b1d82..77dc55c6ef 100644 --- a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Edit /// protected virtual bool AlwaysShowWhenSelected => false; - protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); + protected override bool ShouldBeAlive => (DrawableObject?.IsAlive == true && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); protected HitObjectSelectionBlueprint(HitObject hitObject) : base(hitObject) From 8d94e8f5346b7bf17ee8cf4936ce177533e041b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 14:28:05 +0900 Subject: [PATCH 1382/1621] Enable tests and update expectations --- osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs index fcbdcbe724..692f4ec5b7 100644 --- a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs +++ b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs @@ -13,7 +13,6 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Tests.Mods { [TestFixture] - [Ignore("Currently broken, pending fixes/reworking of ModDifficultyAdjust.")] public class ModDifficultyAdjustTest { // Todo: This shouldn't exist, but is currently required for the mod to accept a new BeatmapDifficulty object... @@ -116,8 +115,16 @@ namespace osu.Game.Tests.Mods testMod.OverallDifficulty.Value = 4; testMod.ResetSettingsToDefaults(); - Assert.That(testMod.DrainRate.Value, Is.EqualTo(10)); - Assert.That(testMod.OverallDifficulty.Value, Is.EqualTo(10)); + Assert.That(testMod.DrainRate.Value, Is.Null); + Assert.That(testMod.OverallDifficulty.Value, Is.Null); + + var applied = applyDifficulty(new BeatmapDifficulty + { + DrainRate = 10, + OverallDifficulty = 10 + }); + + Assert.That(applied.OverallDifficulty, Is.EqualTo(10)); } /// @@ -126,10 +133,12 @@ namespace osu.Game.Tests.Mods /// private BeatmapDifficulty applyDifficulty(BeatmapDifficulty difficulty) { + // ensure that ReadFromDifficulty doesn't pollute the values. + var newDifficulty = difficulty.Clone(); + difficulty.ID = ++currentId; testMod.ReadFromDifficulty(difficulty); - var newDifficulty = new BeatmapDifficulty(); testMod.ApplyToDifficulty(newDifficulty); return newDifficulty; } From 0e4f4a6fde3ca36abd4978b01e9806d0a3d0a6a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 14:28:13 +0900 Subject: [PATCH 1383/1621] Initial storage changes --- .../Mods/CatchModDifficultyAdjust.cs | 29 +++--- .../Mods/OsuModDifficultyAdjust.cs | 29 +++--- .../Mods/TaikoModDifficultyAdjust.cs | 8 +- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 97 +++---------------- 4 files changed, 40 insertions(+), 123 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index bd7a1df2e4..aaa426cd03 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -13,23 +13,23 @@ namespace osu.Game.Rulesets.Catch.Mods public class CatchModDifficultyAdjust : ModDifficultyAdjust, IApplicableToBeatmapProcessor { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] - public BindableNumber CircleSize { get; } = new BindableFloatWithLimitExtension + public Bindable CircleSize { get; } = new Bindable { + /* Precision = 0.1f, MinValue = 1, MaxValue = 10, - Default = 5, - Value = 5, + */ }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] - public BindableNumber ApproachRate { get; } = new BindableFloatWithLimitExtension + public Bindable ApproachRate { get; } = new Bindable { + /* Precision = 0.1f, MinValue = 1, MaxValue = 10, - Default = 5, - Value = 5, + */ }; [SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")] @@ -39,8 +39,9 @@ namespace osu.Game.Rulesets.Catch.Mods { base.ApplyLimits(extended); - CircleSize.MaxValue = extended ? 11 : 10; - ApproachRate.MaxValue = extended ? 11 : 10; + // TODO: reimplement + // CircleSize.MaxValue = extended ? 11 : 10; + // ApproachRate.MaxValue = extended ? 11 : 10; } public override string SettingDescription @@ -61,20 +62,12 @@ namespace osu.Game.Rulesets.Catch.Mods } } - protected override void TransferSettings(BeatmapDifficulty difficulty) - { - base.TransferSettings(difficulty); - - TransferSetting(CircleSize, difficulty.CircleSize); - TransferSetting(ApproachRate, difficulty.ApproachRate); - } - protected override void ApplySettings(BeatmapDifficulty difficulty) { base.ApplySettings(difficulty); - ApplySetting(CircleSize, cs => difficulty.CircleSize = cs); - ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar); + if (CircleSize.Value != null) difficulty.CircleSize = CircleSize.Value.Value; + if (ApproachRate.Value != null) difficulty.ApproachRate = ApproachRate.Value.Value; } public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 1cb25edecf..54e30c56a3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -12,31 +12,32 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModDifficultyAdjust : ModDifficultyAdjust { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] - public BindableNumber CircleSize { get; } = new BindableFloatWithLimitExtension + public Bindable CircleSize { get; } = new Bindable { + /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - Default = 5, - Value = 5, + */ }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] - public BindableNumber ApproachRate { get; } = new BindableFloatWithLimitExtension + public Bindable ApproachRate { get; } = new Bindable { + /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - Default = 5, - Value = 5, + */ }; protected override void ApplyLimits(bool extended) { base.ApplyLimits(extended); - CircleSize.MaxValue = extended ? 11 : 10; - ApproachRate.MaxValue = extended ? 11 : 10; + // TODO: reimplement + // CircleSize.MaxValue = extended ? 11 : 10; + // ApproachRate.MaxValue = extended ? 11 : 10; } public override string SettingDescription @@ -55,20 +56,12 @@ namespace osu.Game.Rulesets.Osu.Mods } } - protected override void TransferSettings(BeatmapDifficulty difficulty) - { - base.TransferSettings(difficulty); - - TransferSetting(CircleSize, difficulty.CircleSize); - TransferSetting(ApproachRate, difficulty.ApproachRate); - } - protected override void ApplySettings(BeatmapDifficulty difficulty) { base.ApplySettings(difficulty); - ApplySetting(CircleSize, cs => difficulty.CircleSize = cs); - ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar); + if (CircleSize.Value != null) difficulty.CircleSize = CircleSize.Value.Value; + if (ApproachRate.Value != null) difficulty.ApproachRate = ApproachRate.Value.Value; } } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index 4006652bd5..902ccdc14e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -12,13 +12,13 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModDifficultyAdjust : ModDifficultyAdjust { [SettingSource("Scroll Speed", "Adjust a beatmap's set scroll speed", LAST_SETTING_ORDER + 1)] - public BindableNumber ScrollSpeed { get; } = new BindableFloat + public Bindable ScrollSpeed { get; } = new Bindable { + /* Precision = 0.05f, MinValue = 0.25f, MaxValue = 4, - Default = 1, - Value = 1, + */ }; public override string SettingDescription @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { base.ApplySettings(difficulty); - ApplySetting(ScrollSpeed, scroll => difficulty.SliderMultiplier *= scroll); + if (ScrollSpeed.Value != null) difficulty.SliderMultiplier = ScrollSpeed.Value.Value; } } } diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index b70eee4e1d..e98bd14720 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -1,13 +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.Beatmaps; +using System; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; -using System; -using System.Collections.Generic; +using osu.Game.Beatmaps; using osu.Game.Configuration; -using System.Linq; namespace osu.Game.Rulesets.Mods { @@ -34,23 +33,23 @@ namespace osu.Game.Rulesets.Mods protected const int LAST_SETTING_ORDER = 2; [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)] - public BindableNumber DrainRate { get; } = new BindableFloatWithLimitExtension + public Bindable DrainRate { get; } = new Bindable { + /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - Default = 5, - Value = 5, + */ }; [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)] - public BindableNumber OverallDifficulty { get; } = new BindableFloatWithLimitExtension + public Bindable OverallDifficulty { get; } = new Bindable { + /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - Default = 5, - Value = 5, + */ }; [SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")] @@ -67,8 +66,9 @@ namespace osu.Game.Rulesets.Mods /// Whether limits should extend beyond sane ranges. protected virtual void ApplyLimits(bool extended) { - DrainRate.MaxValue = extended ? 11 : 10; - OverallDifficulty.MaxValue = extended ? 11 : 10; + // TODO: reimplement + // DrainRate.MaxValue = extended ? 11 : 10; + // OverallDifficulty.MaxValue = extended ? 11 : 10; } public override string SettingDescription @@ -86,89 +86,20 @@ namespace osu.Game.Rulesets.Mods } } - private BeatmapDifficulty difficulty; - public void ReadFromDifficulty(BeatmapDifficulty difficulty) { - if (this.difficulty == null || this.difficulty.ID != difficulty.ID) - { - TransferSettings(difficulty); - this.difficulty = difficulty; - } } public void ApplyToDifficulty(BeatmapDifficulty difficulty) => ApplySettings(difficulty); - /// - /// Transfer initial settings from the beatmap to settings. - /// - /// The beatmap's initial values. - protected virtual void TransferSettings(BeatmapDifficulty difficulty) - { - TransferSetting(DrainRate, difficulty.DrainRate); - TransferSetting(OverallDifficulty, difficulty.OverallDifficulty); - } - - private readonly Dictionary userChangedSettings = new Dictionary(); - - /// - /// Transfer a setting from to a configuration bindable. - /// Only performs the transfer if the user is not currently overriding. - /// - protected void TransferSetting(BindableNumber bindable, T beatmapDefault) - where T : struct, IComparable, IConvertible, IEquatable - { - bindable.UnbindEvents(); - - userChangedSettings.TryAdd(bindable, false); - - bindable.Default = beatmapDefault; - - // users generally choose a difficulty setting and want it to stick across multiple beatmap changes. - // we only want to value transfer if the user hasn't changed the value previously. - if (!userChangedSettings[bindable]) - bindable.Value = beatmapDefault; - - bindable.ValueChanged += _ => userChangedSettings[bindable] = !bindable.IsDefault; - } - - internal override void CopyAdjustedSetting(IBindable target, object source) - { - // if the value is non-bindable, it's presumably coming from an external source (like the API) - therefore presume it is not default. - // if the value is bindable, defer to the source's IsDefault to be able to tell. - userChangedSettings[target] = !(source is IBindable bindableSource) || !bindableSource.IsDefault; - base.CopyAdjustedSetting(target, source); - } - - /// - /// Applies a setting from a configuration bindable using , if it has been changed by the user. - /// - protected void ApplySetting(BindableNumber setting, Action applyFunc) - where T : struct, IComparable, IConvertible, IEquatable - { - if (userChangedSettings.TryGetValue(setting, out bool userChangedSetting) && userChangedSetting) - applyFunc.Invoke(setting.Value); - } - /// /// Apply all custom settings to the provided beatmap. /// /// The beatmap to have settings applied. protected virtual void ApplySettings(BeatmapDifficulty difficulty) { - ApplySetting(DrainRate, dr => difficulty.DrainRate = dr); - ApplySetting(OverallDifficulty, od => difficulty.OverallDifficulty = od); - } - - public override void ResetSettingsToDefaults() - { - base.ResetSettingsToDefaults(); - - if (difficulty != null) - { - // base implementation potentially overwrite modified defaults that came from a beatmap selection. - TransferSettings(difficulty); - } + if (DrainRate.Value != null) difficulty.DrainRate = DrainRate.Value.Value; + if (OverallDifficulty.Value != null) difficulty.OverallDifficulty = OverallDifficulty.Value.Value; } /// From d540156e94c3254ee43a9aa28dfc1f13a01852ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 14:29:30 +0900 Subject: [PATCH 1384/1621] Remove now unnecessary `BeatmapDifficulty.ID` --- osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs index 692f4ec5b7..84cf796835 100644 --- a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs +++ b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs @@ -15,23 +15,12 @@ namespace osu.Game.Tests.Mods [TestFixture] public class ModDifficultyAdjustTest { - // Todo: This shouldn't exist, but is currently required for the mod to accept a new BeatmapDifficulty object... - private int currentId; - private TestModDifficultyAdjust testMod; [SetUp] public void Setup() { - currentId = 0; testMod = new TestModDifficultyAdjust(); - - // Todo: This shouldn't be a thing, but is currently required because this causes the mod to keep track of the bindables internally... - applyDifficulty(new BeatmapDifficulty - { - DrainRate = -1, - OverallDifficulty = -1 - }); } [Test] @@ -136,7 +125,6 @@ namespace osu.Game.Tests.Mods // ensure that ReadFromDifficulty doesn't pollute the values. var newDifficulty = difficulty.Clone(); - difficulty.ID = ++currentId; testMod.ReadFromDifficulty(difficulty); testMod.ApplyToDifficulty(newDifficulty); From bd4b3f5268936c8a440073679cda9645c77c524c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 15:42:29 +0900 Subject: [PATCH 1385/1621] Add catch selection blueprint visual test scene (without tests) --- .../Editor/CatchEditorTestSceneContainer.cs | 66 +++++++++++++++++++ .../CatchSelectionBlueprintTestScene.cs | 24 +++++++ .../TestSceneJuiceStreamSelectionBlueprint.cs | 38 +++++++++++ .../Visual/SelectionBlueprintTestScene.cs | 3 +- 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/CatchEditorTestSceneContainer.cs create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchEditorTestSceneContainer.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchEditorTestSceneContainer.cs new file mode 100644 index 0000000000..158c8edba5 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchEditorTestSceneContainer.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Edit; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public class CatchEditorTestSceneContainer : Container + { + [Cached(typeof(Playfield))] + public readonly ScrollingPlayfield Playfield; + + protected override Container Content { get; } + + public CatchEditorTestSceneContainer() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Width = CatchPlayfield.WIDTH; + Height = 1000; + Padding = new MarginPadding + { + Bottom = 100 + }; + + InternalChildren = new Drawable[] + { + new ScrollingTestContainer(ScrollingDirection.Down) + { + TimeRange = 1000, + RelativeSizeAxes = Axes.Both, + Child = Playfield = new TestCatchPlayfield + { + RelativeSizeAxes = Axes.Both + } + }, + new PlayfieldBorder + { + PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Full }, + Clock = new FramedClock(new StopwatchClock(true)) + }, + Content = new Container + { + RelativeSizeAxes = Axes.Both + } + }; + } + + private class TestCatchPlayfield : CatchEditorPlayfield + { + public TestCatchPlayfield() + : base(new BeatmapDifficulty { CircleSize = 0 }) + { + } + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs new file mode 100644 index 0000000000..dcdc32145b --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public abstract class CatchSelectionBlueprintTestScene : SelectionBlueprintTestScene + { + protected ScrollingHitObjectContainer HitObjectContainer => contentContainer.Playfield.HitObjectContainer; + + protected override Container Content => contentContainer; + + private readonly CatchEditorTestSceneContainer contentContainer; + + protected CatchSelectionBlueprintTestScene() + { + base.Content.Add(contentContainer = new CatchEditorTestSceneContainer()); + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs new file mode 100644 index 0000000000..1b96175020 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -0,0 +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 osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public class TestSceneJuiceStreamSelectionBlueprint : CatchSelectionBlueprintTestScene + { + public TestSceneJuiceStreamSelectionBlueprint() + { + var hitObject = new JuiceStream + { + OriginalX = 100, + StartTime = 100, + Path = new SliderPath(PathType.PerfectCurve, new[] + { + Vector2.Zero, + new Vector2(200, 100), + new Vector2(0, 200), + }), + }; + var controlPoint = new ControlPointInfo(); + controlPoint.Add(0, new TimingControlPoint + { + BeatLength = 100 + }); + hitObject.ApplyDefaults(controlPoint, new BeatmapDifficulty { CircleSize = 0 }); + AddBlueprint(new JuiceStreamSelectionBlueprint(hitObject)); + } + } +} diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index dc12a4999d..c3fb3bfc17 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.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 JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; @@ -23,7 +24,7 @@ namespace osu.Game.Tests.Visual }); } - protected void AddBlueprint(HitObjectSelectionBlueprint blueprint, DrawableHitObject drawableObject) + protected void AddBlueprint(HitObjectSelectionBlueprint blueprint, [CanBeNull] DrawableHitObject drawableObject = null) { Add(blueprint.With(d => { From 8da1335e5fe30a39fcc2b80ec5cc49eb51efed96 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 15:51:46 +0900 Subject: [PATCH 1386/1621] Add catch placement blueprint visual test scenes (without tests) --- .../CatchPlacementBlueprintTestScene.cs | 45 +++++++++++++++++++ ...TestSceneBananaShowerPlacementBlueprint.cs | 28 ++++++++++++ .../TestSceneFruitPlacementBlueprint.cs | 19 ++++++++ .../Visual/PlacementBlueprintTestScene.cs | 2 +- 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs new file mode 100644 index 0000000000..12a8a97338 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs @@ -0,0 +1,45 @@ +// 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.Framework.Timing; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public abstract class CatchPlacementBlueprintTestScene : PlacementBlueprintTestScene + { + protected new ScrollingHitObjectContainer HitObjectContainer => contentContainer.Playfield.HitObjectContainer; + + protected override Container Content => contentContainer; + + private readonly CatchEditorTestSceneContainer contentContainer; + + protected CatchPlacementBlueprintTestScene() + { + base.Content.Add(contentContainer = new CatchEditorTestSceneContainer + { + Clock = new FramedClock(new ManualClock()) + }); + } + + // Unused because AddHitObject is overriden + protected override Container CreateHitObjectContainer() => new Container(); + + protected override void AddHitObject(DrawableHitObject hitObject) + { + contentContainer.Playfield.HitObjectContainer.Add(hitObject); + } + + protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) + { + var result = base.SnapForBlueprint(blueprint); + result.Time = HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition); + return result; + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs new file mode 100644 index 0000000000..dd8a7490b5 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs @@ -0,0 +1,28 @@ +// 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.ControlPoints; +using osu.Game.Rulesets.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public class TestSceneBananaShowerPlacementBlueprint : CatchPlacementBlueprintTestScene + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableBananaShower((BananaShower)hitObject); + + protected override PlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint(); + + protected override void AddHitObject(DrawableHitObject hitObject) + { + // Create nested bananas (but positions are not randomized because beatmap processing is not done). + hitObject.HitObject.ApplyDefaults(new ControlPointInfo(), Beatmap.Value.BeatmapInfo.BaseDifficulty); + + base.AddHitObject(hitObject); + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs new file mode 100644 index 0000000000..c1ce27f204 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.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.Game.Rulesets.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public class TestSceneFruitPlacementBlueprint : CatchPlacementBlueprintTestScene + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject); + + protected override PlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint(); + } +} diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index 2dc77fa72a..130a39c33a 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual protected PlacementBlueprintTestScene() { - Add(HitObjectContainer = CreateHitObjectContainer().With(c => c.Clock = new FramedClock(new StopwatchClock()))); + base.Content.Add(HitObjectContainer = CreateHitObjectContainer().With(c => c.Clock = new FramedClock(new StopwatchClock()))); } [BackgroundDependencyLoader] From ae67409f41aae01d8a8408c2e0763f0668a03e1f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 16:12:02 +0900 Subject: [PATCH 1387/1621] Add a test of fruit placement blueprint --- .../CatchPlacementBlueprintTestScene.cs | 37 ++++++++++++++++++- .../TestSceneFruitPlacementBlueprint.cs | 25 +++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs index 12a8a97338..c99e6626e7 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs @@ -1,18 +1,30 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Framework.Timing; +using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; +using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Catch.Tests.Editor { public abstract class CatchPlacementBlueprintTestScene : PlacementBlueprintTestScene { + protected const double TIME_SNAP = 100; + + protected DrawableCatchHitObject LastObject; + protected new ScrollingHitObjectContainer HitObjectContainer => contentContainer.Playfield.HitObjectContainer; protected override Container Content => contentContainer; @@ -27,18 +39,41 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor }); } + [SetUp] + public void Setup() => Schedule(() => + { + HitObjectContainer.Clear(); + ResetPlacement(); + LastObject = null; + }); + + protected void AddMoveStep(double time, float x) => AddStep($"move to time={time}, x={x}", () => + { + float y = HitObjectContainer.PositionAtTime(time); + Vector2 pos = HitObjectContainer.ToScreenSpace(new Vector2(x, y + HitObjectContainer.DrawHeight)); + InputManager.MoveMouseTo(pos); + }); + + protected void AddClickStep(MouseButton button) => AddStep($"click {button}", () => + { + InputManager.Click(button); + }); + + protected IEnumerable FruitOutlines => Content.ChildrenOfType(); + // Unused because AddHitObject is overriden protected override Container CreateHitObjectContainer() => new Container(); protected override void AddHitObject(DrawableHitObject hitObject) { + LastObject = (DrawableCatchHitObject)hitObject; contentContainer.Playfield.HitObjectContainer.Add(hitObject); } protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) { var result = base.SnapForBlueprint(blueprint); - result.Time = HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition); + result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP; return result; } } diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs index c1ce27f204..4b1c45ae2f 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs @@ -1,12 +1,17 @@ // 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.Utils; using osu.Game.Rulesets.Catch.Edit.Blueprints; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osuTK.Input; namespace osu.Game.Rulesets.Catch.Tests.Editor { @@ -15,5 +20,25 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject); protected override PlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint(); + + [Test] + public void TestFruitPlacementPosition() + { + const double time = 300; + const float x = CatchPlayfield.CENTER_X; + + AddMoveStep(time, x); + AddClickStep(MouseButton.Left); + + AddAssert("outline position is correct", () => + { + var outline = FruitOutlines.Single(); + return Precision.AlmostEquals(outline.X, x) && + Precision.AlmostEquals(outline.Y, HitObjectContainer.PositionAtTime(time)); + }); + + AddAssert("fruit time is correct", () => Precision.AlmostEquals(LastObject.StartTimeBindable.Value, time)); + AddAssert("fruit position is correct", () => Precision.AlmostEquals(LastObject.X, x)); + } } } From 68116aa042a9062547b1fff4a8c9d81fd0e05464 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 16:17:09 +0900 Subject: [PATCH 1388/1621] Fix placement blueprint animation is not running in test scene --- .../Editor/CatchPlacementBlueprintTestScene.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs index c99e6626e7..1d30ae34cd 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs @@ -33,10 +33,9 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor protected CatchPlacementBlueprintTestScene() { - base.Content.Add(contentContainer = new CatchEditorTestSceneContainer - { - Clock = new FramedClock(new ManualClock()) - }); + base.Content.Add(contentContainer = new CatchEditorTestSceneContainer()); + + contentContainer.Playfield.Clock = new FramedClock(new ManualClock()); } [SetUp] From 4ac7d629d7fcd739e2bfac5175113e515ad33157 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 16:36:41 +0900 Subject: [PATCH 1389/1621] Expose current placement blueprint --- osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index 130a39c33a..42cf826bd4 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual public abstract class PlacementBlueprintTestScene : OsuManualInputManagerTestScene, IPlacementHandler { protected readonly Container HitObjectContainer; - private PlacementBlueprint currentBlueprint; + protected PlacementBlueprint CurrentBlueprint { get; private set; } protected PlacementBlueprintTestScene() { @@ -63,9 +63,9 @@ namespace osu.Game.Tests.Visual protected void ResetPlacement() { - if (currentBlueprint != null) - Remove(currentBlueprint); - Add(currentBlueprint = CreateBlueprint()); + if (CurrentBlueprint != null) + Remove(CurrentBlueprint); + Add(CurrentBlueprint = CreateBlueprint()); } public void Delete(HitObject hitObject) @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual { base.Update(); - currentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(currentBlueprint)); + CurrentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(CurrentBlueprint)); } protected virtual SnapResult SnapForBlueprint(PlacementBlueprint blueprint) => From 8ac3015f14dcba91ddb413687e191ac3da7346f5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 16:36:44 +0900 Subject: [PATCH 1390/1621] Add tests of banana shower placement blueprint --- ...TestSceneBananaShowerPlacementBlueprint.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs index dd8a7490b5..3f9db5cbf6 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs @@ -1,13 +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 System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osuTK.Input; namespace osu.Game.Rulesets.Catch.Tests.Editor { @@ -24,5 +30,58 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor base.AddHitObject(hitObject); } + + [Test] + public void TestBasicPlacement() + { + const double start_time = 100; + const double end_time = 500; + + AddMoveStep(start_time, 0); + AddClickStep(MouseButton.Left); + AddMoveStep(end_time, 0); + AddClickStep(MouseButton.Right); + AddAssert("banana shower is placed", () => LastObject is DrawableBananaShower); + AddAssert("start time is correct", () => Precision.AlmostEquals(LastObject.HitObject.StartTime, start_time)); + AddAssert("end time is correct", () => Precision.AlmostEquals(LastObject.HitObject.GetEndTime(), end_time)); + } + + [Test] + public void TestReversePlacement() + { + const double start_time = 100; + const double end_time = 500; + + AddMoveStep(end_time, 0); + AddClickStep(MouseButton.Left); + AddMoveStep(start_time, 0); + AddClickStep(MouseButton.Right); + AddAssert("start time is correct", () => Precision.AlmostEquals(LastObject.HitObject.StartTime, start_time)); + AddAssert("end time is correct", () => Precision.AlmostEquals(LastObject.HitObject.GetEndTime(), end_time)); + } + + [Test] + public void TestFinishWithZeroDuration() + { + AddMoveStep(100, 0); + AddClickStep(MouseButton.Left); + AddClickStep(MouseButton.Right); + AddAssert("banana shower is not placed", () => LastObject == null); + AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == PlacementBlueprint.PlacementState.Waiting); + } + + [Test] + public void TestOpacity() + { + AddMoveStep(100, 0); + AddClickStep(MouseButton.Left); + AddAssert("outline is semitransparent", () => timeSpanOutline.Alpha < 1); + AddMoveStep(200, 0); + AddAssert("outline is opaque", () => Precision.AlmostEquals(timeSpanOutline.Alpha, 1)); + AddMoveStep(100, 0); + AddAssert("outline is semitransparent", () => timeSpanOutline.Alpha < 1); + } + + private TimeSpanOutline timeSpanOutline => Content.ChildrenOfType().Single(); } } From 25b94061fd8b414deaefb1db961d0655e8b350dd Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 16:40:18 +0900 Subject: [PATCH 1391/1621] Fix assert step not waiting for transformation --- .../Editor/TestSceneBananaShowerPlacementBlueprint.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs index 3f9db5cbf6..e3811b7669 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs @@ -75,11 +75,11 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor { AddMoveStep(100, 0); AddClickStep(MouseButton.Left); - AddAssert("outline is semitransparent", () => timeSpanOutline.Alpha < 1); + AddUntilStep("outline is semitransparent", () => Precision.DefinitelyBigger(1, timeSpanOutline.Alpha)); AddMoveStep(200, 0); - AddAssert("outline is opaque", () => Precision.AlmostEquals(timeSpanOutline.Alpha, 1)); + AddUntilStep("outline is opaque", () => Precision.AlmostEquals(timeSpanOutline.Alpha, 1)); AddMoveStep(100, 0); - AddAssert("outline is semitransparent", () => timeSpanOutline.Alpha < 1); + AddUntilStep("outline is semitransparent", () => Precision.DefinitelyBigger(1, timeSpanOutline.Alpha)); } private TimeSpanOutline timeSpanOutline => Content.ChildrenOfType().Single(); From fcee69ffe6c479f1ca226612b56cf4391d0f908b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 15:52:49 +0900 Subject: [PATCH 1392/1621] Fix `ShowsDefaultIndicator` not actually being consumed --- osu.Game/Overlays/Settings/SettingsItem.cs | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 15a0a42d31..c60ad020f0 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -101,10 +101,10 @@ namespace osu.Game.Overlays.Settings public event Action SettingChanged; + private readonly RestoreDefaultValueButton restoreDefaultButton; + protected SettingsItem() { - RestoreDefaultValueButton restoreDefaultButton; - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; @@ -126,14 +126,19 @@ namespace osu.Game.Overlays.Settings // all bindable logic is in constructor intentionally to support "CreateSettingsControls" being used in a context it is // never loaded, but requires bindable storage. - if (controlWithCurrent != null) - { - controlWithCurrent.Current.ValueChanged += _ => SettingChanged?.Invoke(); - controlWithCurrent.Current.DisabledChanged += _ => updateDisabled(); + if (controlWithCurrent == null) + throw new ArgumentException(@$"Control created via {nameof(CreateControl)} must implement {nameof(IHasCurrentValue)}"); - if (ShowsDefaultIndicator) - restoreDefaultButton.Current = controlWithCurrent.Current; - } + controlWithCurrent.Current.ValueChanged += _ => SettingChanged?.Invoke(); + controlWithCurrent.Current.DisabledChanged += _ => updateDisabled(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (ShowsDefaultIndicator) + restoreDefaultButton.Current = controlWithCurrent.Current; } private void updateDisabled() From c4313d6e96b687befbbc9462c4320c3185e24dbf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 15:53:49 +0900 Subject: [PATCH 1393/1621] Initial implementation of new flow (only working for approach rate) --- .../Mods/OsuModDifficultyAdjust.cs | 4 +- .../TestSceneModDifficultyAdjustSettings.cs | 74 ++++++++++++++ .../Mods/ApproachRateSettingsControl.cs | 20 ++++ .../Mods/DifficultyAdjustSettingsControl.cs | 97 +++++++++++++++++++ osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 4 +- 5 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs create mode 100644 osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs create mode 100644 osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 54e30c56a3..82c4a6fd56 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] public Bindable CircleSize { get; } = new Bindable { /* @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods */ }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(ApproachRateSettingsControl))] public Bindable ApproachRate { get; } = new Bindable { /* diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs new file mode 100644 index 0000000000..b1ad92273c --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.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 NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Mods; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneModDifficultyAdjustSettings : OsuManualInputManagerTestScene + { + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create difficulty adjust", () => + { + var modDifficultyAdjust = new OsuModDifficultyAdjust(); + + Child = new Container + { + Size = new Vector2(300), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ChildrenEnumerable = modDifficultyAdjust.CreateSettingsControls(), + }, + } + }; + }); + + setBeatmapWithDifficultyParameters(5); + setBeatmapWithDifficultyParameters(8); + } + + [Test] + public void TestBasic() + { + } + + private void setBeatmapWithDifficultyParameters(float value) + { + AddStep($"set beatmap with all {value}", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap() + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = value, + CircleSize = value, + DrainRate = value, + ApproachRate = value, + } + } + })); + } + } +} diff --git a/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs b/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs new file mode 100644 index 0000000000..15a94cb271 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.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; + +namespace osu.Game.Rulesets.Mods +{ + public class ApproachRateSettingsControl : DifficultyAdjustSettingsControl + { + public ApproachRateSettingsControl() + { + CurrentNumber.Precision = 0.1f; + + CurrentNumber.MinValue = 0; + CurrentNumber.MaxValue = 10; + } + + protected override float UpdateFromDifficulty(BeatmapDifficulty difficulty) => difficulty.ApproachRate; + } +} diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs new file mode 100644 index 0000000000..2b437370ea --- /dev/null +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Rulesets.Mods +{ + // TODO: make abstract once we finish making each implementation. + public class DifficultyAdjustSettingsControl : SettingsItem + { + [Resolved] + private IBindable beatmap { get; set; } + + protected readonly BindableNumber CurrentNumber = new BindableNumber + { + // TODO: these need to be pulled out of the main bindable. + MinValue = 0, + MaxValue = 10, + }; + + protected override Drawable CreateControl() => new ControlDrawable(CurrentNumber); + + private bool isInternalChange; + + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmap.BindValueChanged(b => + { + updateFromDifficulty(); + }, true); + + Current.BindValueChanged(current => + { + if (current.NewValue == null) + updateFromDifficulty(); + }); + + CurrentNumber.BindValueChanged(number => + { + if (!isInternalChange) + Current.Value = number.NewValue; + }); + } + + private void updateFromDifficulty() + { + var difficulty = beatmap.Value.BeatmapInfo.BaseDifficulty; + + if (difficulty == null) + return; + + if (Current.Value == null) + { + isInternalChange = true; + CurrentNumber.Value = UpdateFromDifficulty(difficulty); + isInternalChange = false; + } + } + + // TODO: make abstract + protected virtual float UpdateFromDifficulty(BeatmapDifficulty difficulty) => 0; + + private class ControlDrawable : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + public ControlDrawable(BindableNumber currentNumber) + { + InternalChildren = new Drawable[] + { + new SettingsSlider + { + ShowsDefaultIndicator = false, + Current = currentNumber, + } + }; + + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + } + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index e98bd14720..4b68d9cc3f 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mods protected const int LAST_SETTING_ORDER = 2; - [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)] + [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))] public Bindable DrainRate { get; } = new Bindable { /* @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mods */ }; - [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)] + [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))] public Bindable OverallDifficulty { get; } = new Bindable { /* From a6e94dd4918e70e1e5040b5523ec0958e1fb0355 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 16:40:32 +0900 Subject: [PATCH 1394/1621] Add back extended limits support --- .../Mods/CatchModDifficultyAdjust.cs | 17 ++-- .../Mods/OsuModDifficultyAdjust.cs | 20 ++--- .../Mods/TaikoModDifficultyAdjust.cs | 9 +- .../TestSceneModDifficultyAdjustSettings.cs | 2 +- .../Mods/ApproachRateSettingsControl.cs | 8 -- .../Mods/DifficultyAdjustSettingsControl.cs | 21 +++-- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 84 +++++++++++++++++++ osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 81 ++---------------- 8 files changed, 121 insertions(+), 121 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/DifficultyBindable.cs diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index aaa426cd03..947edb5dd9 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -13,35 +13,28 @@ namespace osu.Game.Rulesets.Catch.Mods public class CatchModDifficultyAdjust : ModDifficultyAdjust, IApplicableToBeatmapProcessor { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] - public Bindable CircleSize { get; } = new Bindable + public DifficultyBindable CircleSize { get; } = new DifficultyBindable { - /* Precision = 0.1f, MinValue = 1, MaxValue = 10, - */ }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] - public Bindable ApproachRate { get; } = new Bindable + public DifficultyBindable ApproachRate { get; } = new DifficultyBindable { - /* Precision = 0.1f, MinValue = 1, MaxValue = 10, - */ }; [SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")] public BindableBool HardRockOffsets { get; } = new BindableBool(); - protected override void ApplyLimits(bool extended) + public CatchModDifficultyAdjust() { - base.ApplyLimits(extended); - - // TODO: reimplement - // CircleSize.MaxValue = extended ? 11 : 10; - // ApproachRate.MaxValue = extended ? 11 : 10; + CircleSize.ExtendedLimits.BindTo(ExtendedLimits); + ApproachRate.ExtendedLimits.BindTo(ExtendedLimits); } public override string SettingDescription diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 82c4a6fd56..403ec2c33d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; @@ -12,32 +11,27 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModDifficultyAdjust : ModDifficultyAdjust { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] - public Bindable CircleSize { get; } = new Bindable + public DifficultyBindable CircleSize { get; } = new DifficultyBindable { - /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - */ + ExtendedMaxValue = 11, }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(ApproachRateSettingsControl))] - public Bindable ApproachRate { get; } = new Bindable + public DifficultyBindable ApproachRate { get; } = new DifficultyBindable { - /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - */ + ExtendedMaxValue = 11, }; - protected override void ApplyLimits(bool extended) + public OsuModDifficultyAdjust() { - base.ApplyLimits(extended); - - // TODO: reimplement - // CircleSize.MaxValue = extended ? 11 : 10; - // ApproachRate.MaxValue = extended ? 11 : 10; + CircleSize.ExtendedLimits.BindTo(ExtendedLimits); + ApproachRate.ExtendedLimits.BindTo(ExtendedLimits); } public override string SettingDescription diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index 902ccdc14e..110a7eebc8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; @@ -11,14 +10,12 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Scroll Speed", "Adjust a beatmap's set scroll speed", LAST_SETTING_ORDER + 1)] - public Bindable ScrollSpeed { get; } = new Bindable + [SettingSource("Scroll Speed", "Adjust a beatmap's set scroll speed", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] + public DifficultyBindable ScrollSpeed { get; } = new DifficultyBindable { - /* Precision = 0.05f, MinValue = 0.25f, MaxValue = 4, - */ }; public override string SettingDescription @@ -39,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { base.ApplySettings(difficulty); - if (ScrollSpeed.Value != null) difficulty.SliderMultiplier = ScrollSpeed.Value.Value; + if (ScrollSpeed.Value != null) difficulty.SliderMultiplier *= ScrollSpeed.Value.Value; } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index b1ad92273c..7d982a55cb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void setBeatmapWithDifficultyParameters(float value) { - AddStep($"set beatmap with all {value}", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap() + AddStep($"set beatmap with all {value}", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap { BeatmapInfo = new BeatmapInfo { diff --git a/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs b/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs index 15a94cb271..18773afb30 100644 --- a/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs @@ -7,14 +7,6 @@ namespace osu.Game.Rulesets.Mods { public class ApproachRateSettingsControl : DifficultyAdjustSettingsControl { - public ApproachRateSettingsControl() - { - CurrentNumber.Precision = 0.1f; - - CurrentNumber.MinValue = 0; - CurrentNumber.MaxValue = 10; - } - protected override float UpdateFromDifficulty(BeatmapDifficulty difficulty) => difficulty.ApproachRate; } } diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 2b437370ea..1aede3425a 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -17,17 +17,26 @@ namespace osu.Game.Rulesets.Mods [Resolved] private IBindable beatmap { get; set; } - protected readonly BindableNumber CurrentNumber = new BindableNumber - { - // TODO: these need to be pulled out of the main bindable. - MinValue = 0, - MaxValue = 10, - }; + protected readonly BindableNumber CurrentNumber = new BindableNumber(); protected override Drawable CreateControl() => new ControlDrawable(CurrentNumber); private bool isInternalChange; + private DifficultyBindable difficultyBindable; + + public override Bindable Current + { + get => base.Current; + set + { + // intercept and extract the DifficultyBindable. + difficultyBindable = (DifficultyBindable)value; + CurrentNumber.BindTo(difficultyBindable.CurrentNumber); + base.Current = value; + } + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs new file mode 100644 index 0000000000..d721154392 --- /dev/null +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -0,0 +1,84 @@ +// 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; + +namespace osu.Game.Rulesets.Mods +{ + public class DifficultyBindable : Bindable + { + /// + /// Whether the extended limits should be applied to this bindable. + /// + public BindableBool ExtendedLimits { get; } = new BindableBool(); + + /// + /// An internal numeric bindable to hold and propagate min/max/precision. + /// The value of this bindable should not be set. + /// + public readonly BindableFloat CurrentNumber = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + }; + + public float Precision + { + set => CurrentNumber.Precision = value; + } + + public float MinValue + { + set => CurrentNumber.MinValue = value; + } + + private float maxValue; + + public float MaxValue + { + set + { + if (value == maxValue) + return; + + maxValue = value; + updateMaxValue(); + } + } + + private float? extendedMaxValue; + + /// + /// The maximum value to be used when extended limits are applied. + /// + public float? ExtendedMaxValue + { + set + { + if (value == extendedMaxValue) + return; + + extendedMaxValue = value; + updateMaxValue(); + } + } + + public DifficultyBindable() + { + ExtendedLimits.BindValueChanged(_ => updateMaxValue()); + + BindValueChanged(val => + { + // Ensure that in the case serialisation runs in the wrong order (and limit extensions aren't applied yet) the deserialised value is still propagated. + if (val.NewValue != null) + CurrentNumber.MaxValue = MathF.Max(CurrentNumber.MaxValue, val.NewValue.Value); + }); + } + + private void updateMaxValue() + { + CurrentNumber.MaxValue = ExtendedLimits.Value && extendedMaxValue != null ? extendedMaxValue.Value : maxValue; + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 4b68d9cc3f..d636f22dea 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -33,23 +33,21 @@ namespace osu.Game.Rulesets.Mods protected const int LAST_SETTING_ORDER = 2; [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))] - public Bindable DrainRate { get; } = new Bindable + public DifficultyBindable DrainRate { get; } = new DifficultyBindable { - /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - */ + ExtendedMaxValue = 11, }; [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))] - public Bindable OverallDifficulty { get; } = new Bindable + public DifficultyBindable OverallDifficulty { get; } = new DifficultyBindable { - /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - */ + ExtendedMaxValue = 11, }; [SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")] @@ -57,18 +55,8 @@ namespace osu.Game.Rulesets.Mods protected ModDifficultyAdjust() { - ExtendedLimits.BindValueChanged(extend => ApplyLimits(extend.NewValue)); - } - - /// - /// Changes the difficulty adjustment limits. Occurs when the value of is changed. - /// - /// Whether limits should extend beyond sane ranges. - protected virtual void ApplyLimits(bool extended) - { - // TODO: reimplement - // DrainRate.MaxValue = extended ? 11 : 10; - // OverallDifficulty.MaxValue = extended ? 11 : 10; + OverallDifficulty.ExtendedLimits.BindTo(ExtendedLimits); + DrainRate.ExtendedLimits.BindTo(ExtendedLimits); } public override string SettingDescription @@ -101,62 +89,5 @@ namespace osu.Game.Rulesets.Mods if (DrainRate.Value != null) difficulty.DrainRate = DrainRate.Value.Value; if (OverallDifficulty.Value != null) difficulty.OverallDifficulty = OverallDifficulty.Value.Value; } - - /// - /// A that extends its min/max values to support any assigned value. - /// - protected class BindableDoubleWithLimitExtension : BindableDouble - { - public override double Value - { - get => base.Value; - set - { - if (value < MinValue) - MinValue = value; - if (value > MaxValue) - MaxValue = value; - base.Value = value; - } - } - } - - /// - /// A that extends its min/max values to support any assigned value. - /// - protected class BindableFloatWithLimitExtension : BindableFloat - { - public override float Value - { - get => base.Value; - set - { - if (value < MinValue) - MinValue = value; - if (value > MaxValue) - MaxValue = value; - base.Value = value; - } - } - } - - /// - /// A that extends its min/max values to support any assigned value. - /// - protected class BindableIntWithLimitExtension : BindableInt - { - public override int Value - { - get => base.Value; - set - { - if (value < MinValue) - MinValue = value; - if (value > MaxValue) - MaxValue = value; - base.Value = value; - } - } - } } } From bd7c3345881f312d07074c4676f73f8fc532affb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 16:56:16 +0900 Subject: [PATCH 1395/1621] Avoid the need for per-settings control classes --- .../Mods/CatchModDifficultyAdjust.cs | 4 ++++ osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 4 +++- .../Mods/TaikoModDifficultyAdjust.cs | 1 + .../Rulesets/Mods/ApproachRateSettingsControl.cs | 12 ------------ .../Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 6 +----- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 6 ++++++ osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 2 ++ 7 files changed, 17 insertions(+), 18 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 947edb5dd9..80b5244c34 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Mods Precision = 0.1f, MinValue = 1, MaxValue = 10, + ExtendedMaxValue = 11, + ReadFromDifficulty = diff => diff.CircleSize, }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] @@ -26,6 +28,8 @@ namespace osu.Game.Rulesets.Catch.Mods Precision = 0.1f, MinValue = 1, MaxValue = 10, + ExtendedMaxValue = 11, + ReadFromDifficulty = diff => diff.ApproachRate, }; [SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")] diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 403ec2c33d..d93b097663 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -17,15 +17,17 @@ namespace osu.Game.Rulesets.Osu.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, + ReadFromDifficulty = diff => diff.CircleSize, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(ApproachRateSettingsControl))] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] public DifficultyBindable ApproachRate { get; } = new DifficultyBindable { Precision = 0.1f, MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, + ReadFromDifficulty = diff => diff.ApproachRate, }; public OsuModDifficultyAdjust() diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index 110a7eebc8..ace105b21c 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Mods Precision = 0.05f, MinValue = 0.25f, MaxValue = 4, + ReadFromDifficulty = _ => 1, }; public override string SettingDescription diff --git a/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs b/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs deleted file mode 100644 index 18773afb30..0000000000 --- a/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.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.Beatmaps; - -namespace osu.Game.Rulesets.Mods -{ - public class ApproachRateSettingsControl : DifficultyAdjustSettingsControl - { - protected override float UpdateFromDifficulty(BeatmapDifficulty difficulty) => difficulty.ApproachRate; - } -} diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 1aede3425a..ef2b88846a 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -11,7 +11,6 @@ using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods { - // TODO: make abstract once we finish making each implementation. public class DifficultyAdjustSettingsControl : SettingsItem { [Resolved] @@ -69,14 +68,11 @@ namespace osu.Game.Rulesets.Mods if (Current.Value == null) { isInternalChange = true; - CurrentNumber.Value = UpdateFromDifficulty(difficulty); + CurrentNumber.Value = difficultyBindable.ReadFromDifficulty(difficulty); isInternalChange = false; } } - // TODO: make abstract - protected virtual float UpdateFromDifficulty(BeatmapDifficulty difficulty) => 0; - private class ControlDrawable : CompositeDrawable, IHasCurrentValue { private readonly BindableWithCurrent current = new BindableWithCurrent(); diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index d721154392..7b01b1e0c7 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Bindables; +using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Mods { @@ -23,6 +24,11 @@ namespace osu.Game.Rulesets.Mods MaxValue = 10, }; + /// + /// A function that can extract the current value of this setting from a beatmap difficulty for display purposes. + /// + public Func ReadFromDifficulty; + public float Precision { set => CurrentNumber.Precision = value; diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index d636f22dea..06bf7d9a6b 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, + ReadFromDifficulty = diff => diff.DrainRate, }; [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))] @@ -48,6 +49,7 @@ namespace osu.Game.Rulesets.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, + ReadFromDifficulty = diff => diff.OverallDifficulty, }; [SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")] From 88b00123f6efe28c876d954b3a6957ad6febf2b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 17:00:55 +0900 Subject: [PATCH 1396/1621] Use existing reflection methods to avoid manual binding of `ExtendedLimits` --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 6 ------ osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 6 ------ osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 7 +++++-- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 80b5244c34..8686627c2a 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -35,12 +35,6 @@ namespace osu.Game.Rulesets.Catch.Mods [SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")] public BindableBool HardRockOffsets { get; } = new BindableBool(); - public CatchModDifficultyAdjust() - { - CircleSize.ExtendedLimits.BindTo(ExtendedLimits); - ApproachRate.ExtendedLimits.BindTo(ExtendedLimits); - } - public override string SettingDescription { get diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index d93b097663..983216dfa1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -30,12 +30,6 @@ namespace osu.Game.Rulesets.Osu.Mods ReadFromDifficulty = diff => diff.ApproachRate, }; - public OsuModDifficultyAdjust() - { - CircleSize.ExtendedLimits.BindTo(ExtendedLimits); - ApproachRate.ExtendedLimits.BindTo(ExtendedLimits); - } - public override string SettingDescription { get diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 06bf7d9a6b..d9d305d457 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -57,8 +57,11 @@ namespace osu.Game.Rulesets.Mods protected ModDifficultyAdjust() { - OverallDifficulty.ExtendedLimits.BindTo(ExtendedLimits); - DrainRate.ExtendedLimits.BindTo(ExtendedLimits); + foreach (var (_, property) in this.GetOrderedSettingsSourceProperties()) + { + if (property.GetValue(this) is DifficultyBindable diffAdjustBindable) + diffAdjustBindable.ExtendedLimits.BindTo(ExtendedLimits); + } } public override string SettingDescription From 533db01cc0782e3e31465e04efe1d19ce5bb189d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 17:14:51 +0900 Subject: [PATCH 1397/1621] Add comprehensive tests of difficulty adjust settings --- .../TestSceneModDifficultyAdjustSettings.cs | 143 +++++++++++++++++- 1 file changed, 137 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index 7d982a55cb..d0a02a6c2d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.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.Graphics; using osu.Framework.Graphics.Containers; @@ -8,6 +9,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osuTK; using osuTK.Graphics; @@ -16,12 +19,14 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneModDifficultyAdjustSettings : OsuManualInputManagerTestScene { + private OsuModDifficultyAdjust modDifficultyAdjust; + [SetUpSteps] public void SetUpSteps() { - AddStep("create difficulty adjust", () => + AddStep("create control", () => { - var modDifficultyAdjust = new OsuModDifficultyAdjust(); + modDifficultyAdjust = new OsuModDifficultyAdjust(); Child = new Container { @@ -44,14 +49,140 @@ namespace osu.Game.Tests.Visual.UserInterface } }; }); - - setBeatmapWithDifficultyParameters(5); - setBeatmapWithDifficultyParameters(8); } [Test] - public void TestBasic() + public void TestFollowsBeatmapDefaultsVisually() { + setBeatmapWithDifficultyParameters(5); + + checkSliderAtValue("Circle Size", 5); + checkBindableAtValue("Circle Size", null); + + setBeatmapWithDifficultyParameters(8); + + checkSliderAtValue("Circle Size", 8); + checkBindableAtValue("Circle Size", null); + } + + [Test] + public void TestOutOfRangeValueStillApplied() + { + AddStep("set override cs to 11", () => modDifficultyAdjust.CircleSize.Value = 11); + + checkSliderAtValue("Circle Size", 11); + checkBindableAtValue("Circle Size", 11); + + // this is a no-op, just showing that it won't reset the value during deserialisation. + setExtendedLimits(false); + + checkSliderAtValue("Circle Size", 11); + checkBindableAtValue("Circle Size", 11); + + // setting extended limits will reset the serialisation exception. + // this should be fine as the goal is to allow, at most, the value of extended limits. + setExtendedLimits(true); + + checkSliderAtValue("Circle Size", 11); + checkBindableAtValue("Circle Size", 11); + } + + [Test] + public void TestExtendedLimits() + { + setSliderValue("Circle Size", 99); + + checkSliderAtValue("Circle Size", 10); + checkBindableAtValue("Circle Size", 10); + + setExtendedLimits(true); + + checkSliderAtValue("Circle Size", 10); + checkBindableAtValue("Circle Size", 10); + + setSliderValue("Circle Size", 99); + + checkSliderAtValue("Circle Size", 11); + checkBindableAtValue("Circle Size", 11); + + setExtendedLimits(false); + + checkSliderAtValue("Circle Size", 10); + checkBindableAtValue("Circle Size", 10); + } + + [Test] + public void TestUserOverrideMaintainedOnBeatmapChange() + { + setSliderValue("Circle Size", 9); + + setBeatmapWithDifficultyParameters(2); + + checkSliderAtValue("Circle Size", 9); + checkBindableAtValue("Circle Size", 9); + } + + [Test] + public void TestResetToDefault() + { + setBeatmapWithDifficultyParameters(2); + + setSliderValue("Circle Size", 9); + checkSliderAtValue("Circle Size", 9); + checkBindableAtValue("Circle Size", 9); + + resetToDefault("Circle Size"); + checkSliderAtValue("Circle Size", 2); + checkBindableAtValue("Circle Size", null); + } + + [Test] + public void TestUserOverrideMaintainedOnMatchingBeatmapValue() + { + setBeatmapWithDifficultyParameters(2); + + checkSliderAtValue("Circle Size", 2); + checkBindableAtValue("Circle Size", null); + + setSliderValue("Circle Size", 9); + checkSliderAtValue("Circle Size", 9); + checkBindableAtValue("Circle Size", 9); + + setBeatmapWithDifficultyParameters(4); + + checkSliderAtValue("Circle Size", 9); + checkBindableAtValue("Circle Size", 9); + } + + private void resetToDefault(string name) + { + AddStep($"Reset {name} to default", () => + this.ChildrenOfType().First(c => c.LabelText == name) + .Current.SetDefault()); + } + + private void setExtendedLimits(bool status) => + AddStep($"Set extended limits {status}", () => modDifficultyAdjust.ExtendedLimits.Value = status); + + private void setSliderValue(string name, float value) + { + AddStep($"Set {name} slider to {value}", () => + this.ChildrenOfType().First(c => c.LabelText == name) + .ChildrenOfType>().First().Current.Value = value); + } + + private void checkBindableAtValue(string name, float? expectedValue) + { + AddAssert($"Bindable {name} is {(expectedValue?.ToString() ?? "null")}", () => + this.ChildrenOfType().First(c => c.LabelText == name) + .Current.Value == expectedValue); + } + + private void checkSliderAtValue(string name, float expectedValue) + { + AddAssert($"Slider {name} at {expectedValue}", () => + this.ChildrenOfType().First(c => c.LabelText == name) + .ChildrenOfType>().First().Current.Value == expectedValue); } private void setBeatmapWithDifficultyParameters(float value) From 52ea62e3b2176d3da8fe83e689da62694e01fda5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 17:39:59 +0900 Subject: [PATCH 1398/1621] Add more comments and xmldoc --- .../Mods/DifficultyAdjustSettingsControl.cs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index ef2b88846a..05a36d3d31 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -16,9 +16,13 @@ namespace osu.Game.Rulesets.Mods [Resolved] private IBindable beatmap { get; set; } - protected readonly BindableNumber CurrentNumber = new BindableNumber(); + /// + /// Used to track the display value on the setting slider. + /// This can either be a user override or the beatmap default (when is null). + /// + private readonly BindableNumber displayNumber = new BindableNumber(); - protected override Drawable CreateControl() => new ControlDrawable(CurrentNumber); + protected override Drawable CreateControl() => new ControlDrawable(displayNumber); private bool isInternalChange; @@ -31,7 +35,10 @@ namespace osu.Game.Rulesets.Mods { // intercept and extract the DifficultyBindable. difficultyBindable = (DifficultyBindable)value; - CurrentNumber.BindTo(difficultyBindable.CurrentNumber); + + // this bind is used to transfer bounds/precision only. + displayNumber.BindTo(difficultyBindable.CurrentNumber); + base.Current = value; } } @@ -40,18 +47,18 @@ namespace osu.Game.Rulesets.Mods { base.LoadComplete(); - beatmap.BindValueChanged(b => - { - updateFromDifficulty(); - }, true); + beatmap.BindValueChanged(b => updateFromDifficulty(), true); Current.BindValueChanged(current => { + // the user override has changed; transfer the correct value to the visual display. if (current.NewValue == null) updateFromDifficulty(); + else + displayNumber.Value = current.NewValue.Value; }); - CurrentNumber.BindValueChanged(number => + displayNumber.BindValueChanged(number => { if (!isInternalChange) Current.Value = number.NewValue; @@ -67,8 +74,9 @@ namespace osu.Game.Rulesets.Mods if (Current.Value == null) { + // ensure the beatmap's value is not transferred as a user override. isInternalChange = true; - CurrentNumber.Value = difficultyBindable.ReadFromDifficulty(difficulty); + displayNumber.Value = difficultyBindable.ReadFromDifficulty(difficulty); isInternalChange = false; } } @@ -77,6 +85,8 @@ namespace osu.Game.Rulesets.Mods { private readonly BindableWithCurrent current = new BindableWithCurrent(); + // Mainly just for fulfilling the interface requirements. + // The actual update flow is done via the provided number. public Bindable Current { get => current.Current; From ba939c0b657aa8bd683635b100bac9d2335c2e7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 17:40:09 +0900 Subject: [PATCH 1399/1621] Simplify serialisation edge case by moving to `Value` override --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index 7b01b1e0c7..22d8472922 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -73,13 +73,19 @@ namespace osu.Game.Rulesets.Mods public DifficultyBindable() { ExtendedLimits.BindValueChanged(_ => updateMaxValue()); + } - BindValueChanged(val => + public override float? Value + { + get => base.Value; + set { // Ensure that in the case serialisation runs in the wrong order (and limit extensions aren't applied yet) the deserialised value is still propagated. - if (val.NewValue != null) - CurrentNumber.MaxValue = MathF.Max(CurrentNumber.MaxValue, val.NewValue.Value); - }); + if (value != null) + CurrentNumber.MaxValue = MathF.Max(CurrentNumber.MaxValue, value.Value); + + base.Value = value; + } } private void updateMaxValue() From c67f756c754426af42eecc6d5ccb19a23f4cdc5d Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 8 Jul 2021 11:52:43 +0300 Subject: [PATCH 1400/1621] Change approach rate bonuses to be less punishing on mid-length maps --- .../Difficulty/OsuPerformanceCalculator.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 749d7d1b41..40ae1fca91 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -98,11 +98,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) - approachRateFactor += 0.4 * (Attributes.ApproachRate - 10.33); + approachRateFactor += Attributes.ApproachRate - 10.33; else if (Attributes.ApproachRate < 8.0) - approachRateFactor += 0.01 * (8.0 - Attributes.ApproachRate); + approachRateFactor += 0.025 * (8.0 - Attributes.ApproachRate); - aimValue *= 1.0 + Math.Min(approachRateFactor, approachRateFactor * (totalHits / 1000.0)); + aimValue *= 1.0 + (0.03 + 0.37 * (1.0 / Math.Exp(-(0.0007 * (totalHits - 400))))) * approachRateFactor; // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. if (mods.Any(h => h is OsuModHidden)) @@ -145,10 +145,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) - approachRateFactor += 0.4 * (Attributes.ApproachRate - 10.33); + approachRateFactor = Attributes.ApproachRate - 10.33; speedValue *= 1.0 + Math.Min(approachRateFactor, approachRateFactor * (totalHits / 1000.0)); + double approachRateFactor = 0.0; + if (Attributes.ApproachRate > 10.33) + approachRateFactor += Attributes.ApproachRate - 10.33; + + speedValue *= 1.0 + (0.03 + 0.37 * (1.0 / Math.Exp(-(0.0007 * (totalHits - 400))))) * approachRateFactor; + if (mods.Any(m => m is OsuModHidden)) speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); From 592d3fdf006f63fd020cb04197f24523a104c304 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 8 Jul 2021 11:54:58 +0300 Subject: [PATCH 1401/1621] Cleanup --- .../Difficulty/OsuPerformanceCalculator.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 40ae1fca91..d53af98f92 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -98,9 +98,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) - approachRateFactor += Attributes.ApproachRate - 10.33; + approachRateFactor = Attributes.ApproachRate - 10.33; else if (Attributes.ApproachRate < 8.0) - approachRateFactor += 0.025 * (8.0 - Attributes.ApproachRate); + approachRateFactor = 0.025 * (8.0 - Attributes.ApproachRate); aimValue *= 1.0 + (0.03 + 0.37 * (1.0 / Math.Exp(-(0.0007 * (totalHits - 400))))) * approachRateFactor; @@ -147,12 +147,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (Attributes.ApproachRate > 10.33) approachRateFactor = Attributes.ApproachRate - 10.33; - speedValue *= 1.0 + Math.Min(approachRateFactor, approachRateFactor * (totalHits / 1000.0)); - - double approachRateFactor = 0.0; - if (Attributes.ApproachRate > 10.33) - approachRateFactor += Attributes.ApproachRate - 10.33; - speedValue *= 1.0 + (0.03 + 0.37 * (1.0 / Math.Exp(-(0.0007 * (totalHits - 400))))) * approachRateFactor; if (mods.Any(m => m is OsuModHidden)) From af270cccc436e581bb9c987a20d8c425a65e4675 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 03:13:44 +0900 Subject: [PATCH 1402/1621] Fix cross talk between `ModSelectOverlay`s --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 6c47b92d29..98a79a62c8 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -429,7 +429,7 @@ namespace osu.Game.Overlays.Mods if (!Stacked) modEnumeration = ModUtils.FlattenMods(modEnumeration); - section.Mods = modEnumeration.Select(getValidModOrNull).Where(m => m != null); + section.Mods = modEnumeration.Select(getValidModOrNull).Where(m => m != null).Select(m => m.CreateCopy()); } updateSelectedButtons(); From 7153983dd4d8c62209967195e269523fc6bdd76c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 18:29:52 +0900 Subject: [PATCH 1403/1621] Add test coverage --- .../TestSceneModSelectOverlay.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 4a1d90d871..91edc226c9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania.Mods; @@ -50,6 +51,38 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("show", () => modSelect.Show()); } + /// + /// Ensure that two mod overlays are not cross polluting via central settings instances. + /// + [Test] + public void TestSettingsNotCrossPolluting() + { + Bindable> selectedMods2 = null; + + AddStep("select diff adjust", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }); + + AddStep("set setting", () => modSelect.ChildrenOfType>().First().Current.Value = 8); + + AddAssert("ensure setting is propagated", () => SelectedMods.Value.OfType().Single().CircleSize.Value == 8); + + AddStep("create second bindable", () => selectedMods2 = new Bindable>(new Mod[] { new OsuModDifficultyAdjust() })); + + AddStep("create second overlay", () => + { + Add(modSelect = new TestModSelectOverlay().With(d => + { + d.Origin = Anchor.TopCentre; + d.Anchor = Anchor.TopCentre; + d.SelectedMods.BindTarget = selectedMods2; + })); + }); + + AddStep("show", () => modSelect.Show()); + + AddAssert("ensure first is unchanged", () => SelectedMods.Value.OfType().Single().CircleSize.Value == 8); + AddAssert("ensure second is default", () => selectedMods2.Value.OfType().Single().CircleSize.Value == 5); + } + [Test] public void TestSettingsResetOnDeselection() { From c937c45360228a8f8d11808d7d3903fa7cf2227f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 18:49:32 +0900 Subject: [PATCH 1404/1621] Don't move selected objects outside the playfield in catch editor --- .../Edit/CatchSelectionHandler.cs | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index d35d74d93d..9fcfa22cac 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -1,9 +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 System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -26,13 +29,19 @@ namespace osu.Game.Rulesets.Catch.Edit Vector2 targetPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint + moveEvent.ScreenSpaceDelta); float deltaX = targetPosition.X - originalPosition.X; + foreach (float x in EditorBeatmap.SelectedHitObjects.SelectMany(getOriginalPositions)) + deltaX = Math.Clamp(deltaX, -x, CatchPlayfield.WIDTH - x); + + if (deltaX == 0) + { + // Returns true: even there is no positional change, there may be a time change. + return true; + } + EditorBeatmap.PerformOnSelection(h => { if (!(h is CatchHitObject hitObject)) return; - if (hitObject is BananaShower) return; - - // TODO: confine in bounds hitObject.OriginalX += deltaX; // Move the nested hit objects to give an instant result before nested objects are recreated. @@ -42,5 +51,37 @@ namespace osu.Game.Rulesets.Catch.Edit return true; } + + /// + /// Enumerate X positions that should be contained in-bounds after move offset is applied. + /// + private IEnumerable getOriginalPositions(HitObject hitObject) + { + switch (hitObject) + { + case Fruit fruit: + yield return fruit.OriginalX; + + break; + + case JuiceStream juiceStream: + foreach (var nested in juiceStream.NestedHitObjects.OfType()) + { + // Exclude tiny droplets: even if `OriginalX` is outside the playfield, it can be moved inside the playfield after the random offset application. + if (!(nested is TinyDroplet)) + yield return nested.OriginalX; + } + + break; + + case BananaShower _: + // A banana shower occupies the whole screen width. + // If the selection contains a banana shower, the selection cannot be moved horizontally. + yield return 0; + yield return CatchPlayfield.WIDTH; + + break; + } + } } } From 93eb385dd40e3d745691c09d8e4376354ae40bd8 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 8 Jul 2021 20:01:39 +0900 Subject: [PATCH 1405/1621] Add sound for switching between volume controls --- osu.Game/Overlays/Volume/VolumeMeter.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index eed6f5c2f3..4822f411c5 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -42,7 +43,8 @@ namespace osu.Game.Overlays.Volume private Container selectedGlowContainer; - private Sample sample; + private Sample hoverSample; + private Sample notchSample; private double sampleLastPlaybackTime; public event Action StateChanged; @@ -78,7 +80,8 @@ namespace osu.Game.Overlays.Volume [BackgroundDependencyLoader] private void load(OsuColour colours, AudioManager audio) { - sample = audio.Samples.Get(@"UI/notch-tick"); + hoverSample = audio.Samples.Get($"UI/{HoverSampleSet.Button.GetDescription()}-hover"); + notchSample = audio.Samples.Get(@"UI/notch-tick"); sampleLastPlaybackTime = Time.Current; Color4 backgroundColour = colours.Gray1; @@ -274,7 +277,7 @@ namespace osu.Game.Overlays.Volume if (Time.Current - sampleLastPlaybackTime <= tick_debounce_time) return; - var channel = sample.GetChannel(); + var channel = notchSample.GetChannel(); channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + displayVolume * 0.1f; @@ -395,6 +398,8 @@ namespace osu.Game.Overlays.Volume selectedGlowContainer.FadeOut(transition_length, Easing.OutExpo); break; } + + hoverSample?.Play(); } } } From 546f55d34114e94b4e09cb959149b4bb704a8f17 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 8 Jul 2021 20:23:11 +0900 Subject: [PATCH 1406/1621] Change profile section expansion to use dropdown sounds --- .../Containers/OsuClickableContainer.cs | 2 +- osu.Game/Online/Chat/DrawableLinkCompiler.cs | 2 +- .../Header/Components/ExpandDetailsButton.cs | 18 ++++++++++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 0bc3c876e1..2caa864580 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.Containers protected override Container Content => content; - protected virtual HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet); + protected virtual HoverSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet); public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Default) { diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index e7f47833a2..abd7b73b5f 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -28,7 +28,7 @@ namespace osu.Game.Online.Chat public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); - protected override HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); + protected override HoverSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); public DrawableLinkCompiler(IEnumerable parts) { diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs index 527c70685f..e395ee8a97 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -2,11 +2,14 @@ // 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.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterface; using osuTK; namespace osu.Game.Overlays.Profile.Header.Components @@ -18,18 +21,29 @@ namespace osu.Game.Overlays.Profile.Header.Components public override LocalisableString TooltipText => DetailsVisible.Value ? "collapse" : "expand"; private SpriteIcon icon; + private Sample sampleOpen; + private Sample sampleClose; + + protected override HoverSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new HoverSounds(); public ExpandDetailsButton() { - Action = () => DetailsVisible.Toggle(); + Action = () => + { + DetailsVisible.Toggle(); + (DetailsVisible.Value ? sampleOpen : sampleClose)?.Play(); + }; } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, AudioManager audio) { IdleColour = colourProvider.Background2; HoverColour = colourProvider.Background2.Lighten(0.2f); + sampleOpen = audio.Samples.Get(@"UI/dropdown-open"); + sampleClose = audio.Samples.Get(@"UI/dropdown-close"); + Child = icon = new SpriteIcon { Anchor = Anchor.Centre, From 8746ef0ba9c3924c09eb6881b05af3b36a011b3c Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 8 Jul 2021 20:36:25 +0900 Subject: [PATCH 1407/1621] Avoid double playback of sample --- osu.Game/Overlays/Volume/VolumeMeter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 4822f411c5..f4cbbf5a00 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -391,6 +391,7 @@ namespace osu.Game.Overlays.Volume case SelectionState.Selected: this.ScaleTo(1.04f, transition_length, Easing.OutExpo); selectedGlowContainer.FadeIn(transition_length, Easing.OutExpo); + hoverSample?.Play(); break; case SelectionState.NotSelected: @@ -398,8 +399,6 @@ namespace osu.Game.Overlays.Volume selectedGlowContainer.FadeOut(transition_length, Easing.OutExpo); break; } - - hoverSample?.Play(); } } } From b7803b889e5560adbd11e325c9421322d3b39ee2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 20:37:38 +0900 Subject: [PATCH 1408/1621] Rename control class to be more descriptive --- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 05a36d3d31..da5fb516f6 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mods /// private readonly BindableNumber displayNumber = new BindableNumber(); - protected override Drawable CreateControl() => new ControlDrawable(displayNumber); + protected override Drawable CreateControl() => new SliderControl(displayNumber); private bool isInternalChange; @@ -81,19 +81,20 @@ namespace osu.Game.Rulesets.Mods } } - private class ControlDrawable : CompositeDrawable, IHasCurrentValue + private class SliderControl : CompositeDrawable, IHasCurrentValue { private readonly BindableWithCurrent current = new BindableWithCurrent(); // Mainly just for fulfilling the interface requirements. // The actual update flow is done via the provided number. + // Of note, this is used for the "reset to default" flow. public Bindable Current { get => current.Current; set => current.Current = value; } - public ControlDrawable(BindableNumber currentNumber) + public SliderControl(BindableNumber currentNumber) { InternalChildren = new Drawable[] { From 02298c2cf40e2b7df25d44c056d9507cb338d1fc Mon Sep 17 00:00:00 2001 From: StanR Date: Fri, 9 Jul 2021 00:06:05 +0300 Subject: [PATCH 1409/1621] Fix incorrect curve, move total hits factor into a separate variable for clarity --- .../Difficulty/OsuPerformanceCalculator.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index d53af98f92..8a3c426381 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -102,7 +102,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty else if (Attributes.ApproachRate < 8.0) approachRateFactor = 0.025 * (8.0 - Attributes.ApproachRate); - aimValue *= 1.0 + (0.03 + 0.37 * (1.0 / Math.Exp(-(0.0007 * (totalHits - 400))))) * approachRateFactor; + double approachRateTotalHitsFactor = 1.0 / (1.0 + Math.Exp(-(0.007 * (totalHits - 400)))); + + aimValue *= 1.0 + (0.03 + 0.37 * approachRateTotalHitsFactor) * approachRateFactor; // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. if (mods.Any(h => h is OsuModHidden)) @@ -147,7 +149,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (Attributes.ApproachRate > 10.33) approachRateFactor = Attributes.ApproachRate - 10.33; - speedValue *= 1.0 + (0.03 + 0.37 * (1.0 / Math.Exp(-(0.0007 * (totalHits - 400))))) * approachRateFactor; + double approachRateTotalHitsFactor = 1.0 / (1.0 + Math.Exp(-(0.007 * (totalHits - 400)))); + + speedValue *= 1.0 + (0.03 + 0.37 * approachRateTotalHitsFactor) * approachRateFactor; if (mods.Any(m => m is OsuModHidden)) speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); From a7be6327709a8e59561b257c21786c133860100a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 9 Jul 2021 00:39:09 +0300 Subject: [PATCH 1410/1621] Improve documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 44adb70108..7413837852 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -23,12 +23,8 @@ namespace osu.Game.Overlays // this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button. public override bool AcceptsFocus => true; - // this is intentionally not using BindableWithCurrent as this needs a BindableNumber instance for an accurate IsDefault value. - // - // this also cannot use IBindableWithCurrent.Create() due to BindableNumberWithCurrent - // directly casting given bindables to BindableNumber, which is not necessarily the case. - // - // therefore rely on the old method of taking each current bindable instance for now, until things are settled framework-side. + // this is intentionally not using BindableWithCurrent, as it can use the wrong IsDefault implementation when passed a BindableNumber. + // using GetBoundCopy() ensures that the received bindable is of the exact same type as the source bindable and uses the proper IsDefault implementation. private Bindable current; public Bindable Current From 0223c569df8395004aca92aa8923275b9ea22b61 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 9 Jul 2021 00:48:48 +0300 Subject: [PATCH 1411/1621] Remove no longer necessary method definitions --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 7413837852..469d48d82b 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -35,9 +35,9 @@ namespace osu.Game.Overlays current?.UnbindAll(); current = value.GetBoundCopy(); - current.ValueChanged += onValueChanged; - current.DefaultChanged += onDefaultChanged; - current.DisabledChanged += onDisabledChanged; + current.ValueChanged += _ => UpdateState(); + current.DefaultChanged += _ => UpdateState(); + current.DisabledChanged += _ => UpdateState(); UpdateState(); } } @@ -93,10 +93,6 @@ namespace osu.Game.Overlays UpdateState(); } - private void onValueChanged(ValueChangedEvent _) => UpdateState(); - private void onDefaultChanged(ValueChangedEvent _) => UpdateState(); - private void onDisabledChanged(bool _) => UpdateState(); - public void UpdateState() => Scheduler.AddOnce(updateState); private void updateState() From 2eb12a59b7d07979aabc95dfb1396452e41e72e0 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 9 Jul 2021 11:16:47 +0900 Subject: [PATCH 1412/1621] Rename function to be more accurate --- osu.Game/Graphics/Containers/OsuClickableContainer.cs | 4 ++-- osu.Game/Online/Chat/DrawableLinkCompiler.cs | 2 +- .../Overlays/Profile/Header/Components/ExpandDetailsButton.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 2caa864580..bf397e4251 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.Containers protected override Container Content => content; - protected virtual HoverSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet); + protected virtual HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet); public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Default) { @@ -39,7 +39,7 @@ namespace osu.Game.Graphics.Containers InternalChildren = new Drawable[] { content, - CreateHoverClickSounds(sampleSet) + CreateHoverSounds(sampleSet) }; } } diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index abd7b73b5f..53ea1d6f99 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -28,7 +28,7 @@ namespace osu.Game.Online.Chat public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); - protected override HoverSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); public DrawableLinkCompiler(IEnumerable parts) { diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs index e395ee8a97..16b443875e 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private Sample sampleOpen; private Sample sampleClose; - protected override HoverSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new HoverSounds(); + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(); public ExpandDetailsButton() { From 9f7c6adb5849777f77de6ba48c129bf8b53fd130 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 12:15:28 +0900 Subject: [PATCH 1413/1621] Fix test failures due to logger pollution As seen at https://github.com/ppy/osu/pull/13831/checks?check_run_id=3025050307. I can't confirm that this will fix the issue but it looks like the only plausible reason. I have confirmed that the logging is not coming from the local (first logging is guaranteed to be after `SetupForRun`). --- osu.Game/Tests/CleanRunHeadlessGameHost.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index baa7b27d28..03ab94d1da 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -25,8 +25,11 @@ namespace osu.Game.Tests protected override void SetupForRun() { - base.SetupForRun(); Storage.DeleteDirectory(string.Empty); + + // base call needs to be run *after* storage is emptied, as it updates the (static) logger's storage and may start writing + // log entries from another source if a unit test host is shared over multiple tests, causing a file access denied exception. + base.SetupForRun(); } } } From 887035c12e85861704ed243ee2b3f5a9eb8ef2f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 12:21:24 +0900 Subject: [PATCH 1414/1621] Fix migration target having left over files potentially causing test failures As seen at https://github.com/ppy/osu/pull/13831/checks?check_run_id=3025050324. --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index a540ad7247..4c44e2ec72 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -184,6 +184,9 @@ namespace osu.Game.Tests.NonVisual Assert.DoesNotThrow(() => osu.Migrate(customPath2)); Assert.That(File.Exists(Path.Combine(customPath2, database_filename))); + // some files may have been left behind for whatever reason, but that's not what we're testing here. + customPath = prepareCustomPath(); + Assert.DoesNotThrow(() => osu.Migrate(customPath)); Assert.That(File.Exists(Path.Combine(customPath, database_filename))); } From 9786e1a932426971cc09f11009785a842da70a3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 12:36:54 +0900 Subject: [PATCH 1415/1621] Ensure run-from-screen song select reaches correct point in execution Fixes issues as seen at https://github.com/ppy/osu/runs/3023581865?check_suite_focus=true. Song select may take a few frames to perform initial selection as there is a bit of internal async logic. This ensures that the beatmap has been updated before continuing with test execution. --- .../Visual/Navigation/TestScenePerformFromScreen.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 92152bce18..4ec76e1e4b 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -58,8 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestPerformAtSongSelectFromPlayerLoader() { - AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); - PushAndConfirm(() => new TestPlaySongSelect()); + importAndWaitForSongSelect(); AddStep("Press enter", () => InputManager.Key(Key.Enter)); AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader); @@ -72,8 +71,7 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestPerformAtMenuFromPlayerLoader() { - AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); - PushAndConfirm(() => new TestPlaySongSelect()); + importAndWaitForSongSelect(); AddStep("Press enter", () => InputManager.Key(Key.Enter)); AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader); @@ -172,6 +170,13 @@ namespace osu.Game.Tests.Visual.Navigation } } + private void importAndWaitForSongSelect() + { + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); + PushAndConfirm(() => new TestPlaySongSelect()); + AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineBeatmapSetID == 241526); + } + public class DialogBlockingScreen : OsuScreen { [Resolved] From 7e146796066588763ab367a6915e32c7a1f5d902 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 9 Jul 2021 12:58:08 +0900 Subject: [PATCH 1416/1621] Expand the selection movement limiting code with detailed comments --- .../Edit/CatchSelectionHandler.cs | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 9fcfa22cac..d3e79e1778 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -27,10 +27,9 @@ namespace osu.Game.Rulesets.Catch.Edit var blueprint = moveEvent.Blueprint; Vector2 originalPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint); Vector2 targetPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint + moveEvent.ScreenSpaceDelta); - float deltaX = targetPosition.X - originalPosition.X; - foreach (float x in EditorBeatmap.SelectedHitObjects.SelectMany(getOriginalPositions)) - deltaX = Math.Clamp(deltaX, -x, CatchPlayfield.WIDTH - x); + float deltaX = targetPosition.X - originalPosition.X; + deltaX = limitMovement(deltaX, EditorBeatmap.SelectedHitObjects); if (deltaX == 0) { @@ -52,6 +51,36 @@ namespace osu.Game.Rulesets.Catch.Edit return true; } + /// + /// Limit positional movement of the objects by the constraint that moved objects should stay in bounds. + /// + /// The positional movement. + /// The objects to be moved. + /// The positional movement with the restriction applied. + private float limitMovement(float deltaX, IEnumerable movingObjects) + { + float minX = float.PositiveInfinity; + float maxX = float.NegativeInfinity; + + foreach (float x in movingObjects.SelectMany(getOriginalPositions)) + { + minX = Math.Min(minX, x); + maxX = Math.Max(maxX, x); + } + + // To make an object with position `x` stay in bounds after `deltaX` movement, `0 <= x + deltaX <= WIDTH` should be satisfied. + // Subtracting `x`, we get `-x <= deltaX <= WIDTH - x`. + // We only need to apply the inequality to extreme values of `x`. + float lowerBound = -minX; + float upperBound = CatchPlayfield.WIDTH - maxX; + // The inequality may be unsatisfiable if the objects were already out of bounds. + // In that case, don't move objects at all. + if (!(lowerBound <= upperBound)) + return 0; + + return Math.Clamp(deltaX, lowerBound, upperBound); + } + /// /// Enumerate X positions that should be contained in-bounds after move offset is applied. /// From f3b68a4fbf5d8e810f8451299ac4b57ad138228d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 13:17:25 +0900 Subject: [PATCH 1417/1621] Fix storage wrapping logic setting logger too early in startup sequence --- osu.Game/IO/OsuStorage.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 75130b0f9b..802c71e363 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -102,8 +102,15 @@ namespace osu.Game.IO protected override void ChangeTargetStorage(Storage newStorage) { + var lastStorage = UnderlyingStorage; base.ChangeTargetStorage(newStorage); - Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs"); + + if (lastStorage != null) + { + // for now we assume that if there was a previous storage, this is a migration operation. + // the logger shouldn't be set during initialisation as it can cause cross-talk in tests (due to being static). + Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs"); + } } public override void Migrate(Storage newStorage) From df4bd86cfc946e8ca44a24edf8dd21b78b174ecf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 13:17:25 +0900 Subject: [PATCH 1418/1621] Fix storage wrapping logic setting logger too early in startup sequence --- osu.Game/IO/OsuStorage.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 75130b0f9b..802c71e363 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -102,8 +102,15 @@ namespace osu.Game.IO protected override void ChangeTargetStorage(Storage newStorage) { + var lastStorage = UnderlyingStorage; base.ChangeTargetStorage(newStorage); - Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs"); + + if (lastStorage != null) + { + // for now we assume that if there was a previous storage, this is a migration operation. + // the logger shouldn't be set during initialisation as it can cause cross-talk in tests (due to being static). + Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs"); + } } public override void Migrate(Storage newStorage) From 90326f8864723bc4427512705da4295a9f316300 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 13:24:26 +0900 Subject: [PATCH 1419/1621] Standardise variables --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 4 ++-- osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs | 2 +- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 2 +- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 4 ++-- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 8686627c2a..bd78d3b085 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Mods MinValue = 1, MaxValue = 10, ExtendedMaxValue = 11, - ReadFromDifficulty = diff => diff.CircleSize, + ReadCurrentFromDifficulty = diff => diff.CircleSize, }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Mods MinValue = 1, MaxValue = 10, ExtendedMaxValue = 11, - ReadFromDifficulty = diff => diff.ApproachRate, + ReadCurrentFromDifficulty = diff => diff.ApproachRate, }; [SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")] diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 983216dfa1..3a6b232f9f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, - ReadFromDifficulty = diff => diff.CircleSize, + ReadCurrentFromDifficulty = diff => diff.CircleSize, }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, - ReadFromDifficulty = diff => diff.ApproachRate, + ReadCurrentFromDifficulty = diff => diff.ApproachRate, }; public override string SettingDescription diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index ace105b21c..9540e35780 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Mods Precision = 0.05f, MinValue = 0.25f, MaxValue = 4, - ReadFromDifficulty = _ => 1, + ReadCurrentFromDifficulty = _ => 1, }; public override string SettingDescription diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index da5fb516f6..9ca44b25e8 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Mods { // ensure the beatmap's value is not transferred as a user override. isInternalChange = true; - displayNumber.Value = difficultyBindable.ReadFromDifficulty(difficulty); + displayNumber.Value = difficultyBindable.ReadCurrentFromDifficulty(difficulty); isInternalChange = false; } } diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index 22d8472922..26538fa4e3 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods /// /// Whether the extended limits should be applied to this bindable. /// - public BindableBool ExtendedLimits { get; } = new BindableBool(); + public readonly BindableBool ExtendedLimits = new BindableBool(); /// /// An internal numeric bindable to hold and propagate min/max/precision. @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mods /// /// A function that can extract the current value of this setting from a beatmap difficulty for display purposes. /// - public Func ReadFromDifficulty; + public Func ReadCurrentFromDifficulty; public float Precision { diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index d9d305d457..b78c30e8a5 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, - ReadFromDifficulty = diff => diff.DrainRate, + ReadCurrentFromDifficulty = diff => diff.DrainRate, }; [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))] @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, - ReadFromDifficulty = diff => diff.OverallDifficulty, + ReadCurrentFromDifficulty = diff => diff.OverallDifficulty, }; [SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")] From f9cd7f10d827836be6080376b2adf5c7b0148ff4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 13:26:00 +0900 Subject: [PATCH 1420/1621] Allow null values for `ReadCurrentFromDifficulty` As long as this isn't a constructor parameter it feels best to gracefully handle omission. Realistically having it in the ctor is the best move, but it doesn't feel great in line with the other parameters passed in via object initalisers. --- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 9ca44b25e8..aa25f20c32 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mods if (difficulty == null) return; - if (Current.Value == null) + if (Current.Value == null && difficultyBindable.ReadCurrentFromDifficulty != null) { // ensure the beatmap's value is not transferred as a user override. isInternalChange = true; From 51bd83b3f4998915cab2a055ad1012c6259b2775 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 13:30:14 +0900 Subject: [PATCH 1421/1621] Update override matching test to match expectations --- .../TestSceneModDifficultyAdjustSettings.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index d0a02a6c2d..bf494d4362 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -139,19 +139,22 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestUserOverrideMaintainedOnMatchingBeatmapValue() { - setBeatmapWithDifficultyParameters(2); + setBeatmapWithDifficultyParameters(3); - checkSliderAtValue("Circle Size", 2); + checkSliderAtValue("Circle Size", 3); checkBindableAtValue("Circle Size", null); - setSliderValue("Circle Size", 9); - checkSliderAtValue("Circle Size", 9); - checkBindableAtValue("Circle Size", 9); + // need to initially change it away from the current beatmap value to trigger an override. + setSliderValue("Circle Size", 4); + setSliderValue("Circle Size", 3); + + checkSliderAtValue("Circle Size", 3); + checkBindableAtValue("Circle Size", 3); setBeatmapWithDifficultyParameters(4); - checkSliderAtValue("Circle Size", 9); - checkBindableAtValue("Circle Size", 9); + checkSliderAtValue("Circle Size", 3); + checkBindableAtValue("Circle Size", 3); } private void resetToDefault(string name) From e0277763d0f7f504ba5e003cce10ba7ac2938632 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 13:50:07 +0900 Subject: [PATCH 1422/1621] Refactor `DifficultyAdjustSettingsControl` to help with readability --- .../Mods/DifficultyAdjustSettingsControl.cs | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index aa25f20c32..9286dd58a9 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -18,11 +18,14 @@ namespace osu.Game.Rulesets.Mods /// /// Used to track the display value on the setting slider. - /// This can either be a user override or the beatmap default (when is null). /// - private readonly BindableNumber displayNumber = new BindableNumber(); + /// + /// When the mod is overriding a default, this will match the value of . + /// When there is no override (ie. is null), this value will match the beatmap provided default via . + /// + private readonly BindableNumber sliderDisplayCurrent = new BindableNumber(); - protected override Drawable CreateControl() => new SliderControl(displayNumber); + protected override Drawable CreateControl() => new SliderControl(sliderDisplayCurrent); private bool isInternalChange; @@ -33,11 +36,10 @@ namespace osu.Game.Rulesets.Mods get => base.Current; set { - // intercept and extract the DifficultyBindable. + // Intercept and extract the internal number bindable from DifficultyBindable. + // This will provide bounds and precision specifications for the slider bar. difficultyBindable = (DifficultyBindable)value; - - // this bind is used to transfer bounds/precision only. - displayNumber.BindTo(difficultyBindable.CurrentNumber); + sliderDisplayCurrent.BindTo(difficultyBindable.CurrentNumber); base.Current = value; } @@ -51,15 +53,22 @@ namespace osu.Game.Rulesets.Mods Current.BindValueChanged(current => { - // the user override has changed; transfer the correct value to the visual display. - if (current.NewValue == null) - updateFromDifficulty(); + if (current.NewValue != null) + { + // a user override has been added or updated. + sliderDisplayCurrent.Value = current.NewValue.Value; + } else - displayNumber.Value = current.NewValue.Value; + { + // user override was removed, so restore the beatmap provided value. + updateFromDifficulty(); + } }); - displayNumber.BindValueChanged(number => + sliderDisplayCurrent.BindValueChanged(number => { + // this handles the transfer of the slider value to the main bindable. + // as such, should be skipped if the slider is being updated via updateFromDifficulty(). if (!isInternalChange) Current.Value = number.NewValue; }); @@ -76,23 +85,16 @@ namespace osu.Game.Rulesets.Mods { // ensure the beatmap's value is not transferred as a user override. isInternalChange = true; - displayNumber.Value = difficultyBindable.ReadCurrentFromDifficulty(difficulty); + sliderDisplayCurrent.Value = difficultyBindable.ReadCurrentFromDifficulty(difficulty); isInternalChange = false; } } private class SliderControl : CompositeDrawable, IHasCurrentValue { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - // Mainly just for fulfilling the interface requirements. - // The actual update flow is done via the provided number. - // Of note, this is used for the "reset to default" flow. - public Bindable Current - { - get => current.Current; - set => current.Current = value; - } + // This is required as SettingsItem relies heavily on this bindable for internal use. + // The actual update flow is done via the bindable provided in the constructor. + public Bindable Current { get; set; } = new Bindable(); public SliderControl(BindableNumber currentNumber) { From 741062a6da3a7ad7d3126210585511ac2abdc4fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 13:58:44 +0900 Subject: [PATCH 1423/1621] Simplify bindable update methods --- .../Mods/DifficultyAdjustSettingsControl.cs | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 9286dd58a9..0f48ed4190 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -21,12 +21,15 @@ namespace osu.Game.Rulesets.Mods /// /// /// When the mod is overriding a default, this will match the value of . - /// When there is no override (ie. is null), this value will match the beatmap provided default via . + /// When there is no override (ie. is null), this value will match the beatmap provided default via . /// private readonly BindableNumber sliderDisplayCurrent = new BindableNumber(); protected override Drawable CreateControl() => new SliderControl(sliderDisplayCurrent); + /// + /// Guards against beatmap values displayed on slider bars being transferred to user override. + /// private bool isInternalChange; private DifficultyBindable difficultyBindable; @@ -49,21 +52,8 @@ namespace osu.Game.Rulesets.Mods { base.LoadComplete(); - beatmap.BindValueChanged(b => updateFromDifficulty(), true); - - Current.BindValueChanged(current => - { - if (current.NewValue != null) - { - // a user override has been added or updated. - sliderDisplayCurrent.Value = current.NewValue.Value; - } - else - { - // user override was removed, so restore the beatmap provided value. - updateFromDifficulty(); - } - }); + Current.BindValueChanged(current => updateCurrentFromSlider()); + beatmap.BindValueChanged(b => updateCurrentFromSlider(), true); sliderDisplayCurrent.BindValueChanged(number => { @@ -74,20 +64,27 @@ namespace osu.Game.Rulesets.Mods }); } - private void updateFromDifficulty() + private void updateCurrentFromSlider() { + if (Current.Value != null) + { + // a user override has been added or updated. + sliderDisplayCurrent.Value = Current.Value.Value; + return; + } + var difficulty = beatmap.Value.BeatmapInfo.BaseDifficulty; if (difficulty == null) return; - if (Current.Value == null && difficultyBindable.ReadCurrentFromDifficulty != null) - { - // ensure the beatmap's value is not transferred as a user override. - isInternalChange = true; - sliderDisplayCurrent.Value = difficultyBindable.ReadCurrentFromDifficulty(difficulty); - isInternalChange = false; - } + // generally should always be implemented, else the slider will have a zero default. + if (difficultyBindable.ReadCurrentFromDifficulty == null) + return; + + isInternalChange = true; + sliderDisplayCurrent.Value = difficultyBindable.ReadCurrentFromDifficulty(difficulty); + isInternalChange = false; } private class SliderControl : CompositeDrawable, IHasCurrentValue From 6a5f0e823718cb5cd419eab59ebcff55c159aa37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 14:28:57 +0900 Subject: [PATCH 1424/1621] Move handling of replay seek operations out of progress bar This is in order to avoid using the now obsoleted property `SliderBar.AllowKeyboardInputWhenNotHovered` (see https://github.com/ppy/osu-framework/pull/4579). --- .../Input/Bindings/GlobalActionContainer.cs | 8 +++++ osu.Game/Screens/Play/ReplayPlayer.cs | 32 +++++++++++++++++++ osu.Game/Screens/Play/SongProgressBar.cs | 2 -- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 2482be90ee..d3cc90ef99 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -87,6 +87,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), new KeyBinding(InputKey.Space, GlobalAction.TogglePauseReplay), + new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward), + new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward), new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), }; @@ -272,5 +274,11 @@ namespace osu.Game.Input.Bindings [Description("Next volume meter")] NextVolumeMeter, + + [Description("Seek replay forward")] + SeekReplayForward, + + [Description("Seek replay backward")] + SeekReplayBackward, } } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index f70c05c2ff..adbb5a53f6 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -3,11 +3,15 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using osu.Framework.Input.Bindings; +using osu.Framework.Threading; using osu.Game.Beatmaps; +using osu.Game.Extensions; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -43,10 +47,24 @@ namespace osu.Game.Screens.Play protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false); + private ScheduledDelegate keyboardSeekDelegate; + public bool OnPressed(GlobalAction action) { + const double keyboard_seek_amount = 5000; + switch (action) { + case GlobalAction.SeekReplayBackward: + keyboardSeekDelegate?.Cancel(); + keyboardSeekDelegate = this.BeginKeyRepeat(Scheduler, () => keyboardSeek(-1)); + return true; + + case GlobalAction.SeekReplayForward: + keyboardSeekDelegate?.Cancel(); + keyboardSeekDelegate = this.BeginKeyRepeat(Scheduler, () => keyboardSeek(1)); + return true; + case GlobalAction.TogglePauseReplay: if (GameplayClockContainer.IsPaused.Value) GameplayClockContainer.Start(); @@ -56,10 +74,24 @@ namespace osu.Game.Screens.Play } return false; + + void keyboardSeek(int direction) + { + double target = Math.Clamp(GameplayClockContainer.CurrentTime + direction * keyboard_seek_amount, 0, GameplayBeatmap.HitObjects.Last().GetEndTime()); + + Seek(target); + } } public void OnReleased(GlobalAction action) { + switch (action) + { + case GlobalAction.SeekReplayBackward: + case GlobalAction.SeekReplayForward: + keyboardSeekDelegate?.Cancel(); + break; + } } } } diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 939b5fad1f..5052b32335 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -57,8 +57,6 @@ namespace osu.Game.Screens.Play set => CurrentNumber.Value = value; } - protected override bool AllowKeyboardInputWhenNotHovered => true; - public SongProgressBar(float barHeight, float handleBarHeight, Vector2 handleSize) { CurrentNumber.MinValue = 0; From 9083b28114039b18078b0b0cc1402d15de88ed87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 14:47:11 +0900 Subject: [PATCH 1425/1621] Add test coverage of seeking and pausing --- .../Visual/Gameplay/TestSceneReplayPlayer.cs | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs new file mode 100644 index 0000000000..fcd65eaff3 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs @@ -0,0 +1,87 @@ +// 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.Rulesets; +using osu.Game.Rulesets.Osu; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneReplayPlayer : RateAdjustedBeatmapTestScene + { + protected TestReplayPlayer Player; + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("Initialise player", () => Player = CreatePlayer(new OsuRuleset())); + AddStep("Load player", () => LoadScreen(Player)); + AddUntilStep("player loaded", () => Player.IsLoaded); + } + + [Test] + public void TestPause() + { + double? lastTime = null; + + AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0); + + AddStep("Pause playback", () => InputManager.Key(Key.Space)); + + AddUntilStep("Time stopped progressing", () => + { + double current = Player.GameplayClockContainer.CurrentTime; + bool changed = lastTime != current; + lastTime = current; + + return !changed; + }); + + AddWaitStep("wait some", 10); + + AddAssert("Time still stopped", () => lastTime == Player.GameplayClockContainer.CurrentTime); + } + + [Test] + public void TestSeekBackwards() + { + double? lastTime = null; + + AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0); + + AddStep("Seek backwards", () => + { + lastTime = Player.GameplayClockContainer.CurrentTime; + InputManager.Key(Key.Left); + }); + + AddAssert("Jumped backwards", () => Player.GameplayClockContainer.CurrentTime - lastTime < 0); + } + + [Test] + public void TestSeekForwards() + { + double? lastTime = null; + + AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0); + + AddStep("Seek forwards", () => + { + lastTime = Player.GameplayClockContainer.CurrentTime; + InputManager.Key(Key.Right); + }); + + AddAssert("Jumped forwards", () => Player.GameplayClockContainer.CurrentTime - lastTime > 500); + } + + protected TestReplayPlayer CreatePlayer(Ruleset ruleset) + { + Beatmap.Value = CreateWorkingBeatmap(ruleset.RulesetInfo); + SelectedMods.Value = new[] { ruleset.GetAutoplayMod() }; + + return new TestReplayPlayer(false); + } + } +} From 995ef953c689746c546457b9ec477368a89af366 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 9 Jul 2021 15:13:54 +0900 Subject: [PATCH 1426/1621] Modify comment --- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index d3e79e1778..02dc6f61c8 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Edit if (deltaX == 0) { - // Returns true: even there is no positional change, there may be a time change. + // Even there is no positional change, there may be a time change. return true; } @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Edit case JuiceStream juiceStream: foreach (var nested in juiceStream.NestedHitObjects.OfType()) { - // Exclude tiny droplets: even if `OriginalX` is outside the playfield, it can be moved inside the playfield after the random offset application. + // Even if `OriginalX` is outside the playfield, tiny droplets can be moved inside the playfield after the random offset application. if (!(nested is TinyDroplet)) yield return nested.OriginalX; } From 6ae631b03a1ac6048f94a29e97f370fa26454a58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 16:50:54 +0900 Subject: [PATCH 1427/1621] Remove previous seek testing logic from common test scene --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index bc7cf8eee2..fdc3916c47 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -11,7 +11,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.Break; using osu.Game.Screens.Ranking; -using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { @@ -36,18 +35,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); - double? time = null; - - AddStep("store time", () => time = Player.GameplayClockContainer.GameplayClock.CurrentTime); - - // test seek via keyboard - AddStep("seek with right arrow key", () => InputManager.Key(Key.Right)); - AddAssert("time seeked forward", () => Player.GameplayClockContainer.GameplayClock.CurrentTime > time + 2000); - - AddStep("store time", () => time = Player.GameplayClockContainer.GameplayClock.CurrentTime); - AddStep("seek with left arrow key", () => InputManager.Key(Key.Left)); - AddAssert("time seeked backward", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < time); - seekToBreak(0); seekToBreak(1); From a9250a0d984acdbcc6a92daebc1009cae3e2feaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 18:23:27 +0900 Subject: [PATCH 1428/1621] Limit update notifications to once per startup This logic was intentionally designed to continue to prompt the user to update if they haven't, but that seems pretty anti-user. The change will stop the update prompts from showing more than once per game startup, unless manually invoked by the user a second time. Closes https://github.com/ppy/osu/issues/13821. --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 58d67c11d9..73f53721d1 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -68,6 +68,8 @@ namespace osu.Desktop.Updater return false; } + scheduleRecheck = false; + if (notification == null) { notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active }; @@ -98,7 +100,6 @@ namespace osu.Desktop.Updater // could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) // try again without deltas. await checkForUpdateAsync(false, notification).ConfigureAwait(false); - scheduleRecheck = false; } else { From 494089e402dcd358dc6d1e20eadf8736fa858407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Jul 2021 11:22:54 +0200 Subject: [PATCH 1429/1621] Fix up English in comment --- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 02dc6f61c8..51ccdb7410 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Edit if (deltaX == 0) { - // Even there is no positional change, there may be a time change. + // Even if there is no positional change, there may be a time change. return true; } From c5011865fca0f93672703d183b3268d5071cbd86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Jul 2021 11:23:38 +0200 Subject: [PATCH 1430/1621] Invert strangely negated condition --- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 51ccdb7410..7eebf04ca2 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Edit float upperBound = CatchPlayfield.WIDTH - maxX; // The inequality may be unsatisfiable if the objects were already out of bounds. // In that case, don't move objects at all. - if (!(lowerBound <= upperBound)) + if (lowerBound > upperBound) return 0; return Math.Clamp(deltaX, lowerBound, upperBound); From b705213ea930473e8f84f4cc92cb96fbc3dcb89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Jul 2021 11:44:32 +0200 Subject: [PATCH 1431/1621] Update test to match expectations after refactor --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 91edc226c9..3485d7fbc3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("show", () => modSelect.Show()); AddAssert("ensure first is unchanged", () => SelectedMods.Value.OfType().Single().CircleSize.Value == 8); - AddAssert("ensure second is default", () => selectedMods2.Value.OfType().Single().CircleSize.Value == 5); + AddAssert("ensure second is default", () => selectedMods2.Value.OfType().Single().CircleSize.Value == null); } [Test] From e10b7867c158059e9f041edbdbc5ac10011f2203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Jul 2021 12:13:36 +0200 Subject: [PATCH 1432/1621] Rewrite method again to hopefully help readability --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 54 ++++++++++++---------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 4c856ef4a0..cd63b618bd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// The that this slider has been shifted by. private Vector2 moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) { - var area = getSliderPlacementArea(slider); + var area = calculatePossibleMovementBounds(slider); var prevPosition = slider.Position; @@ -192,46 +192,52 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Calculates a that includes all possible positions of the slider such that - /// the entire slider is inside the playfield. + /// Calculates a which contains all of the possible movements of the slider (in relative X/Y coordinates) + /// such that the entire slider is inside the playfield. /// /// /// If the slider is larger than the playfield, the returned may have negative width/height. /// - private RectangleF getSliderPlacementArea(Slider slider) + private RectangleF calculatePossibleMovementBounds(Slider slider) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); - // Initially, assume that the slider can be placed anywhere in the playfield - var box = new RectangleF(Vector2.Zero, OsuPlayfield.BASE_SIZE); + float minX = float.PositiveInfinity; + float maxX = float.NegativeInfinity; - // Then narrow down the area with each path position + float minY = float.PositiveInfinity; + float maxY = float.NegativeInfinity; + + // Compute the bounding box of the slider. foreach (var pos in pathPositions) { - // Reduce Width and Height accordingly after increasing X and Y - // to keep the right and bottom edge of the rectangle in place - var right = box.Right; - box.X = Math.Max(box.X, -pos.X); - box.Width = right - box.X; + minX = MathF.Min(minX, pos.X); + maxX = MathF.Max(maxX, pos.X); - var bottom = box.Bottom; - box.Y = Math.Max(box.Y, -pos.Y); - box.Height = bottom - box.Y; - - box.Width = Math.Min(box.Width, OsuPlayfield.BASE_SIZE.X - pos.X - box.X); - box.Height = Math.Min(box.Height, OsuPlayfield.BASE_SIZE.Y - pos.Y - box.Y); + minY = MathF.Min(minY, pos.Y); + maxY = MathF.Max(maxY, pos.Y); } - // Reduce the area by slider radius, so that the slider fits inside the playfield completely + // Take the circle radius into account. var radius = (float)slider.Radius; - box.X += radius; - box.Y += radius; - box.Width -= radius * 2; - box.Height -= radius * 2; + minX -= radius; + minY -= radius; - return box; + maxX += radius; + maxY += radius; + + // Given the bounding box of the slider (via min/max X/Y), + // the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right), + // and the amount that it can move to the right is WIDTH - maxX. + // Same calculation applies for the Y axis. + float left = -minX; + float right = OsuPlayfield.BASE_SIZE.X - maxX; + float top = -minY; + float bottom = OsuPlayfield.BASE_SIZE.Y - maxY; + + return new RectangleF(left, top, right - left, bottom - top); } /// From 5cd11a02baeea3cd5f5766dcaf8bf1cabb424d16 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 10 Jul 2021 17:56:37 +0700 Subject: [PATCH 1433/1621] add autolink test --- .../UserInterface/TestSceneOsuMarkdownContainer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index 931af7bc95..82e26cb87d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -95,6 +95,15 @@ _**italic with underscore, bold with asterisk**_"; }); } + [Test] + public void TestAutoLink() + { + AddStep("Add autolink", () => + { + markdownContainer.Text = ""; + }); + } + [Test] public void TestInlineCode() { From 45ff28f83baae57d8a8ae4fc2f6c4f9f9ff04b2f Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 10 Jul 2021 17:57:33 +0700 Subject: [PATCH 1434/1621] add autolink constructor --- .../Graphics/Containers/Markdown/OsuMarkdownLinkText.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs index f91a0e40e3..82e556f653 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs @@ -26,6 +26,12 @@ namespace osu.Game.Graphics.Containers.Markdown title = linkInline.Title; } + public OsuMarkdownLinkText(AutolinkInline autolinkInline) + : base(autolinkInline) + { + text = autolinkInline.Url; + } + [BackgroundDependencyLoader] private void load() { From e4f13e311ea7f36672ed9f8027c5ee597f3ca3ee Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 10 Jul 2021 17:58:00 +0700 Subject: [PATCH 1435/1621] override add auto link in text flow container --- .../Containers/Markdown/OsuMarkdownTextFlowContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index 36b48b7769..a7cd6b3905 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -17,6 +17,9 @@ namespace osu.Game.Graphics.Containers.Markdown protected override void AddLinkText(string text, LinkInline linkInline) => AddDrawable(new OsuMarkdownLinkText(text, linkInline)); + protected override void AddAutoLink(AutolinkInline autolinkInline) + => AddDrawable(new OsuMarkdownLinkText(autolinkInline)); + protected override void AddImage(LinkInline linkInline) => AddDrawable(new OsuMarkdownImage(linkInline)); // TODO : Change font to monospace From c44558e3c880ab5bf1384449acbb575b2267d4a8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 10 Jul 2021 17:57:52 +0300 Subject: [PATCH 1436/1621] Add back `LoadComplete` override --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 469d48d82b..fd3ee16fe6 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -78,6 +78,12 @@ namespace osu.Game.Overlays }; } + protected override void LoadComplete() + { + base.LoadComplete(); + UpdateState(); + } + public LocalisableString TooltipText => "revert to default"; protected override bool OnHover(HoverEvent e) From a1f3adc32004200198b227e3047c391ef828b754 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 10 Jul 2021 19:56:44 +0300 Subject: [PATCH 1437/1621] Add simple test cases --- .../Visual/Settings/TestSceneSettingsItem.cs | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs index f63145f534..ebcdc8c702 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs @@ -4,7 +4,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Overlays.Settings; using osu.Game.Overlays; @@ -17,28 +16,63 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestRestoreDefaultValueButtonVisibility() { - TestSettingsTextBox textBox = null; + SettingsTextBox textBox = null; + RestoreDefaultValueButton restoreDefaultValueButton = null; - AddStep("create settings item", () => Child = textBox = new TestSettingsTextBox + AddStep("create settings item", () => { - Current = new Bindable + Child = textBox = new SettingsTextBox { - Default = "test", - Value = "test" - } + Current = new Bindable + { + Default = "test", + Value = "test" + } + }; + + restoreDefaultValueButton = textBox.ChildrenOfType>().Single(); }); - AddAssert("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0); + AddAssert("restore button hidden", () => restoreDefaultValueButton.Alpha == 0); AddStep("change value from default", () => textBox.Current.Value = "non-default"); - AddUntilStep("restore button shown", () => textBox.RestoreDefaultValueButton.Alpha > 0); + AddUntilStep("restore button shown", () => restoreDefaultValueButton.Alpha > 0); AddStep("restore default", () => textBox.Current.SetDefault()); - AddUntilStep("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0); + AddUntilStep("restore button hidden", () => restoreDefaultValueButton.Alpha == 0); } - private class TestSettingsTextBox : SettingsTextBox + /// + /// Tests precision of the restore default value button, with a couple of single floating-point numbers that could potentially underflow. + /// + [TestCase(4.2f)] + [TestCase(9.9f)] + public void TestRestoreDefaultValueButtonPrecision(float initialValue) { - public Drawable RestoreDefaultValueButton => this.ChildrenOfType>().Single(); + SettingsSlider sliderBar = null; + RestoreDefaultValueButton restoreDefaultValueButton = null; + + AddStep("create settings item", () => + { + Child = sliderBar = new SettingsSlider + { + Current = new BindableFloat(initialValue) + { + MinValue = 0f, + MaxValue = 10f, + Precision = 0.1f, + } + }; + + restoreDefaultValueButton = sliderBar.ChildrenOfType>().Single(); + }); + + AddAssert("restore button hidden", () => restoreDefaultValueButton.Alpha == 0); + + AddStep("change value to 5.1f", () => sliderBar.Current.Value = 5.0f); + AddUntilStep("restore button shown", () => restoreDefaultValueButton.Alpha > 0); + + AddStep("restore default", () => sliderBar.Current.SetDefault()); + AddUntilStep("restore button hidden", () => restoreDefaultValueButton.Alpha == 0); } } -} \ No newline at end of file +} From 07ede7a14745c3b1adf4f22e704f1a5aa5529d75 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Jul 2021 03:34:57 +0300 Subject: [PATCH 1438/1621] Disallow custom rulesets from score submission --- osu.Game/Screens/Play/SoloPlayer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index ef1087dd62..d90e8e0168 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; +using osu.Game.Rulesets; using osu.Game.Scoring; namespace osu.Game.Screens.Play @@ -27,7 +28,7 @@ namespace osu.Game.Screens.Play if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) return null; - if (!(Ruleset.Value.ID is int rulesetId)) + if (!(Ruleset.Value.ID is int rulesetId) || Ruleset.Value.ID > ILegacyRuleset.MAX_LEGACY_RULESET_ID) return null; return new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash); From 6b8de2a10b6fc3cdbea511a66a4673c824c60fe8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Jul 2021 03:35:35 +0300 Subject: [PATCH 1439/1621] Add test coverage for excluded cases in score submission --- .../TestScenePlayerScoreSubmission.cs | 92 +++++++++++++++---- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index f9ccb10778..5ff2e9c439 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.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.Linq; using NUnit.Framework; using osu.Framework.Screens; @@ -10,6 +11,7 @@ using osu.Game.Online.Rooms; using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; @@ -17,17 +19,26 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Gameplay { - public class TestScenePlayerScoreSubmission : OsuPlayerTestScene + public class TestScenePlayerScoreSubmission : PlayerTestScene { protected override bool AllowFail => allowFail; private bool allowFail; + private Func createCustomBeatmap; + private Func createCustomRuleset; + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; protected override bool HasCustomSteps => true; - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); + + protected override Ruleset CreatePlayerRuleset() => createCustomRuleset?.Invoke() ?? new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => createCustomBeatmap?.Invoke(ruleset) ?? createTestBeatmap(ruleset); + + private IBeatmap createTestBeatmap(RulesetInfo ruleset) { var beatmap = (TestBeatmap)base.CreateBeatmap(ruleset); @@ -36,14 +47,12 @@ namespace osu.Game.Tests.Visual.Gameplay return beatmap; } - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); - [Test] public void TestNoSubmissionOnResultsWithNoToken() { prepareTokenResponse(false); - CreateTest(() => allowFail = false); + createPlayerTest(); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -63,7 +72,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => allowFail = false); + createPlayerTest(); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -82,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(false); - CreateTest(() => allowFail = false); + createPlayerTest(); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -99,7 +108,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => allowFail = true); + createPlayerTest(true); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -114,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => allowFail = true); + createPlayerTest(true); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -131,7 +140,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => allowFail = false); + createPlayerTest(); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -144,7 +153,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => allowFail = false); + createPlayerTest(); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -154,18 +163,49 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); } - private void addFakeHit() + [Test] + public void TestNoSubmissionOnLocalBeatmap() { - AddUntilStep("wait for first result", () => Player.Results.Count > 0); + prepareTokenResponse(true); - AddStep("force successfuly hit", () => + createPlayerTest(false, r => { - Player.ScoreProcessor.RevertResult(Player.Results.First()); - Player.ScoreProcessor.ApplyResult(new OsuJudgementResult(Beatmap.Value.Beatmap.HitObjects.First(), new OsuJudgement()) - { - Type = HitResult.Great, - }); + var beatmap = createTestBeatmap(r); + beatmap.BeatmapInfo.OnlineBeatmapID = null; + return beatmap; }); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + addFakeHit(); + + AddStep("exit", () => Player.Exit()); + AddAssert("ensure no submission", () => Player.SubmittedScore == null); + } + + [Test] + public void TestNoSubmissionOnCustomRuleset() + { + prepareTokenResponse(true); + + createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { ID = 10 } }); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + addFakeHit(); + + AddStep("exit", () => Player.Exit()); + AddAssert("ensure no submission", () => Player.SubmittedScore == null); + } + + private void createPlayerTest(bool allowFail = false, Func createBeatmap = null, Func createRuleset = null) + { + CreateTest(() => AddStep("set up requirements", () => + { + this.allowFail = allowFail; + createCustomBeatmap = createBeatmap; + createCustomRuleset = createRuleset; + })); } private void prepareTokenResponse(bool validToken) @@ -188,5 +228,19 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); } + + private void addFakeHit() + { + AddUntilStep("wait for first result", () => Player.Results.Count > 0); + + AddStep("force successfuly hit", () => + { + Player.ScoreProcessor.RevertResult(Player.Results.First()); + Player.ScoreProcessor.ApplyResult(new OsuJudgementResult(Beatmap.Value.Beatmap.HitObjects.First(), new OsuJudgement()) + { + Type = HitResult.Great, + }); + }); + } } } From f21ea3b790812bf48d108601191b11e1b462f087 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Jul 2021 03:46:19 +0300 Subject: [PATCH 1440/1621] Update player test scene `Ruleset` bindable from creation method --- osu.Game/Tests/Visual/PlayerTestScene.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 088e997de9..93491c800f 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -57,7 +57,9 @@ namespace osu.Game.Tests.Visual protected void LoadPlayer() { - var ruleset = Ruleset.Value.CreateInstance(); + var ruleset = CreatePlayerRuleset(); + Ruleset.Value = ruleset.RulesetInfo; + var beatmap = CreateBeatmap(ruleset.RulesetInfo); Beatmap.Value = CreateWorkingBeatmap(beatmap); From 79d546afa2efef11dc7035567ebd9caefaa539e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Jul 2021 10:14:42 +0900 Subject: [PATCH 1441/1621] Add missing osu!catch difficulty adjust attributes --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index bd78d3b085..e59a0a0431 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModDifficultyAdjust : ModDifficultyAdjust, IApplicableToBeatmapProcessor { - [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] public DifficultyBindable CircleSize { get; } = new DifficultyBindable { Precision = 0.1f, @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Mods ReadCurrentFromDifficulty = diff => diff.CircleSize, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] public DifficultyBindable ApproachRate { get; } = new DifficultyBindable { Precision = 0.1f, From 32b4f5fbd60239274af90153d35e4db631986619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 11 Jul 2021 15:12:46 +0200 Subject: [PATCH 1442/1621] Do not store direct references to original bindable `DifficultyAdjustSettingsControl` and its inner `SliderControl` were holding different references to `DifficultyBindable`s from the difficulty adjust mod, therefore leading to bindings being lost to the framework-side automatic unbind logic if the mod was toggled off and back on in rapid succession. Resolve by adding a shadowed implementation of `GetBoundCopy()` and using it to isolate the controls from the mod bindable. --- .../Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 4 ++-- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 0f48ed4190..067657159b 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -41,10 +41,10 @@ namespace osu.Game.Rulesets.Mods { // Intercept and extract the internal number bindable from DifficultyBindable. // This will provide bounds and precision specifications for the slider bar. - difficultyBindable = (DifficultyBindable)value; + difficultyBindable = ((DifficultyBindable)value).GetBoundCopy(); sliderDisplayCurrent.BindTo(difficultyBindable.CurrentNumber); - base.Current = value; + base.Current = difficultyBindable; } } diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index 26538fa4e3..0f16126464 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -92,5 +92,16 @@ namespace osu.Game.Rulesets.Mods { CurrentNumber.MaxValue = ExtendedLimits.Value && extendedMaxValue != null ? extendedMaxValue.Value : maxValue; } + + public new DifficultyBindable GetBoundCopy() => new DifficultyBindable + { + BindTarget = this, + CurrentNumber = { BindTarget = CurrentNumber }, + ExtendedLimits = { BindTarget = ExtendedLimits }, + ReadCurrentFromDifficulty = ReadCurrentFromDifficulty, + // the following is only safe as long as these values are effectively constants. + MaxValue = maxValue, + ExtendedMaxValue = extendedMaxValue + }; } } From c181a724c6e903e61d43f23f5ff12f65ae183788 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 11 Jul 2021 22:01:28 +0800 Subject: [PATCH 1443/1621] Refactor hit object clamping --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 99 +++++++++++++--------- 1 file changed, 59 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index cd63b618bd..e134dcef89 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -67,34 +67,32 @@ namespace osu.Game.Rulesets.Osu.Mods applyRandomisation(rateOfChangeMultiplier, previous, current); - // Move hit objects back into the playfield if they are outside of it, - // which would sometimes happen during big jumps otherwise. - current.PositionRandomised = clampToPlayfield(current.PositionRandomised, (float)hitObject.Radius); + // Move hit objects back into the playfield if they are outside of it + Vector2 shift = Vector2.Zero; - hitObject.Position = current.PositionRandomised; - - // update end position as it may have changed as a result of the position update. - current.EndPositionRandomised = current.PositionRandomised; - - if (hitObject is Slider slider) + if (hitObject is HitCircle circle) { - Vector2 shift = moveSliderIntoPlayfield(slider, current); + shift = clampHitCircleToPlayfield(circle, current); + } + else if (hitObject is Slider slider) + { + shift = clampSliderToPlayfield(slider, current); + } - if (shift != Vector2.Zero) + if (shift != Vector2.Zero) + { + var toBeShifted = new List(); + + for (int j = i - 1; j >= i - objects_to_shift_before_slider && j >= 0; j--) { - var toBeShifted = new List(); + // only shift hit circles + if (!(hitObjects[j] is HitCircle)) break; - for (int j = i - 1; j >= i - objects_to_shift_before_slider && j >= 0; j--) - { - // only shift hit circles - if (!(hitObjects[j] is HitCircle)) break; - - toBeShifted.Add(hitObjects[j]); - } - - if (toBeShifted.Count > 0) - applyDecreasingShift(toBeShifted, shift); + toBeShifted.Add(hitObjects[j]); } + + if (toBeShifted.Count > 0) + applyDecreasingShift(toBeShifted, shift); } previous = current; @@ -145,31 +143,29 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Moves the and all necessary nested s into the if they aren't already. /// - /// The that this slider has been shifted by. - private Vector2 moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo) + /// The deviation from the original randomised position in order to fit within the playfield. + private Vector2 clampSliderToPlayfield(Slider slider, RandomObjectInfo objectInfo) { var area = calculatePossibleMovementBounds(slider); - var prevPosition = slider.Position; + var previousPosition = objectInfo.PositionRandomised; // Clamp slider position to the placement area // If the slider is larger than the playfield, force it to stay at the original position var newX = area.Width < 0 - ? currentObjectInfo.PositionOriginal.X - : Math.Clamp(slider.Position.X, area.Left, area.Right); + ? objectInfo.PositionOriginal.X + : Math.Clamp(previousPosition.X, area.Left, area.Right); var newY = area.Height < 0 - ? currentObjectInfo.PositionOriginal.Y - : Math.Clamp(slider.Position.Y, area.Top, area.Bottom); + ? objectInfo.PositionOriginal.Y + : Math.Clamp(previousPosition.Y, area.Top, area.Bottom); - slider.Position = new Vector2(newX, newY); + slider.Position = objectInfo.PositionRandomised = new Vector2(newX, newY); + objectInfo.EndPositionRandomised = slider.EndPosition; - currentObjectInfo.PositionRandomised = slider.Position; - currentObjectInfo.EndPositionRandomised = slider.EndPosition; + shiftNestedObjects(slider, objectInfo.PositionRandomised - objectInfo.PositionOriginal); - shiftNestedObjects(slider, currentObjectInfo.PositionRandomised - currentObjectInfo.PositionOriginal); - - return slider.Position - prevPosition; + return objectInfo.PositionRandomised - previousPosition; } /// @@ -187,7 +183,7 @@ namespace osu.Game.Rulesets.Osu.Mods // The last object is shifted by a vector slightly larger than zero Vector2 position = hitObject.Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); - hitObject.Position = clampToPlayfield(position, (float)hitObject.Radius); + hitObject.Position = clampToPlayfieldWithPadding(position, (float)hitObject.Radius); } } @@ -256,12 +252,35 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private Vector2 clampToPlayfield(Vector2 position, float radius) + /// + /// Move the randomised position of a hit circle so that it fits inside the playfield. + /// + /// The deviation from the original randomised position in order to fit within the playfield. + private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo) { - position.X = Math.Clamp(position.X, radius, OsuPlayfield.BASE_SIZE.X - radius); - position.Y = Math.Clamp(position.Y, radius, OsuPlayfield.BASE_SIZE.Y - radius); + var previousPosition = objectInfo.PositionRandomised; + objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding( + objectInfo.PositionRandomised, + (float)circle.Radius + ); - return position; + circle.Position = objectInfo.PositionRandomised; + + return objectInfo.PositionRandomised - previousPosition; + } + + /// + /// Clamp a position to playfield, keeping a specified distance from the edges. + /// + /// The position to be clamped. + /// The minimum distance allowed from playfield edges. + /// The clamped position. + private Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding) + { + return new Vector2( + Math.Clamp(position.X, padding, OsuPlayfield.BASE_SIZE.X - padding), + Math.Clamp(position.Y, padding, OsuPlayfield.BASE_SIZE.Y - padding) + ); } private class RandomObjectInfo From 7aecafeecb26f72ca1d2e7a1d2f72b266a2b09a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 11 Jul 2021 16:46:30 +0200 Subject: [PATCH 1444/1621] Rename constant to reflect its purpose --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index e134dcef89..4409017ac9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -27,9 +27,9 @@ namespace osu.Game.Rulesets.Osu.Mods private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; /// - /// Number of previous hit circles to be shifted together when a slider needs to be moved. + /// Number of previous hitobjects to be shifted together when another object is being moved. /// - private const int objects_to_shift_before_slider = 10; + private const int preceding_hitobjects_to_shift = 10; private Random rng; @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var toBeShifted = new List(); - for (int j = i - 1; j >= i - objects_to_shift_before_slider && j >= 0; j--) + for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) { // only shift hit circles if (!(hitObjects[j] is HitCircle)) break; From 63dedb36dec1612c5d68c0293d1d551fbecd57fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 11 Jul 2021 16:49:23 +0200 Subject: [PATCH 1445/1621] Rename variable --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 4409017ac9..f919ecf839 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -146,19 +146,19 @@ namespace osu.Game.Rulesets.Osu.Mods /// The deviation from the original randomised position in order to fit within the playfield. private Vector2 clampSliderToPlayfield(Slider slider, RandomObjectInfo objectInfo) { - var area = calculatePossibleMovementBounds(slider); + var possibleMovementBounds = calculatePossibleMovementBounds(slider); var previousPosition = objectInfo.PositionRandomised; // Clamp slider position to the placement area // If the slider is larger than the playfield, force it to stay at the original position - var newX = area.Width < 0 + var newX = possibleMovementBounds.Width < 0 ? objectInfo.PositionOriginal.X - : Math.Clamp(previousPosition.X, area.Left, area.Right); + : Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right); - var newY = area.Height < 0 + var newY = possibleMovementBounds.Height < 0 ? objectInfo.PositionOriginal.Y - : Math.Clamp(previousPosition.Y, area.Top, area.Bottom); + : Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); slider.Position = objectInfo.PositionRandomised = new Vector2(newX, newY); objectInfo.EndPositionRandomised = slider.EndPosition; From 9e70136100c6550abd53ef07aa36adc0318b2c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 11 Jul 2021 17:26:00 +0200 Subject: [PATCH 1446/1621] Adjust test case slightly --- osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs index ebcdc8c702..df59b9284b 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs @@ -42,12 +42,14 @@ namespace osu.Game.Tests.Visual.Settings } /// - /// Tests precision of the restore default value button, with a couple of single floating-point numbers that could potentially underflow. + /// Ensures that the reset to default button uses the correct implementation of IsDefault to determine whether it should be shown or not. + /// Values have been chosen so that after being set, Value != Default (but they are close enough that the difference is negligible compared to Precision). /// [TestCase(4.2f)] [TestCase(9.9f)] public void TestRestoreDefaultValueButtonPrecision(float initialValue) { + BindableFloat current = null; SettingsSlider sliderBar = null; RestoreDefaultValueButton restoreDefaultValueButton = null; @@ -55,7 +57,7 @@ namespace osu.Game.Tests.Visual.Settings { Child = sliderBar = new SettingsSlider { - Current = new BindableFloat(initialValue) + Current = current = new BindableFloat(initialValue) { MinValue = 0f, MaxValue = 10f, @@ -68,7 +70,7 @@ namespace osu.Game.Tests.Visual.Settings AddAssert("restore button hidden", () => restoreDefaultValueButton.Alpha == 0); - AddStep("change value to 5.1f", () => sliderBar.Current.Value = 5.0f); + AddStep("change value to next closest", () => sliderBar.Current.Value += current.Precision * 0.6f); AddUntilStep("restore button shown", () => restoreDefaultValueButton.Alpha > 0); AddStep("restore default", () => sliderBar.Current.SetDefault()); From a6258d705ed1f188061333ac7299e5c55ca81761 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 11:26:30 +0900 Subject: [PATCH 1447/1621] Make `CurrentNumber` internal --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index 0f16126464..1f76a3e366 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mods /// An internal numeric bindable to hold and propagate min/max/precision. /// The value of this bindable should not be set. /// - public readonly BindableFloat CurrentNumber = new BindableFloat + internal readonly BindableFloat CurrentNumber = new BindableFloat { MinValue = 0, MaxValue = 10, From 3642febbb684180f937b6e9dc06d5b4a5695eade Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 12:35:40 +0900 Subject: [PATCH 1448/1621] Fix one new incorrect formatting inspection from EAP6 --- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 271fbde5c3..8cff790e80 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -31,10 +31,7 @@ namespace osu.Game.Tests.Visual.SongSelect private readonly TestBeatmapDifficultyCache testDifficultyCache = new TestBeatmapDifficultyCache(); [Test] - public void TestLocal([Values("Beatmap", "Some long title and stuff")] - string title, - [Values("Trial", "Some1's very hardest difficulty")] - string version) + public void TestLocal([Values("Beatmap", "Some long title and stuff")] string title, [Values("Trial", "Some1's very hardest difficulty")] string version) { showMetadataForBeatmap(() => CreateWorkingBeatmap(new Beatmap { From f548ba4f6925058b182319d597a0968d2aff29bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 14:07:17 +0900 Subject: [PATCH 1449/1621] Update realm libraries to fix windows 8.1 incompatibility --- 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 8a3c69e40c..f4cf01eb82 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 37dac1a775ee22035b3cf9660883912d7985a0d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 14:36:28 +0900 Subject: [PATCH 1450/1621] Update mobile projects' local references to older realm --- osu.Android.props | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 9280eaf97c..f8c446555d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 2eea646c61..79746be0b3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -99,6 +99,6 @@ - + From 47a593ad7d9bb118eec72e484f7f3a7988139528 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 14:55:09 +0900 Subject: [PATCH 1451/1621] Force a re-check on any exception being thrown --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 73f53721d1..910751a723 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -111,6 +111,7 @@ namespace osu.Desktop.Updater catch (Exception) { // we'll ignore this and retry later. can be triggered by no internet connection or thread abortion. + scheduleRecheck = true; } finally { From 0db316d644064bd168454fcb1354b804ebe23918 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 16:08:20 +0900 Subject: [PATCH 1452/1621] Add password scaffolding --- osu.Game/Online/Rooms/Room.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index b28680ffef..9270c4b69d 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -35,6 +35,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("channel_id")] public readonly Bindable ChannelId = new Bindable(); + [JsonProperty("password")] + public readonly Bindable Password = new Bindable(); + [Cached] [JsonIgnore] public readonly Bindable Category = new Bindable(); @@ -143,6 +146,7 @@ namespace osu.Game.Online.Rooms ChannelId.Value = other.ChannelId.Value; Status.Value = other.Status.Value; + Password.Value = other.Password.Value; Availability.Value = other.Availability.Value; Type.Value = other.Type.Value; MaxParticipants.Value = other.MaxParticipants.Value; From 24f330e5c1ae697f8e02eefb314fcefc559f9d4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 16:12:28 +0900 Subject: [PATCH 1453/1621] Avoid `MatchSettingsOverlay` base class potentially accessing an uninitialised field --- .../OnlinePlay/Match/Components/MatchSettingsOverlay.cs | 4 ++++ .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 7 ++----- .../OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs | 7 ++----- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs index 5699da740c..61bb39d0c5 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs @@ -25,8 +25,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components private void load() { Masking = true; + + Add(Settings = CreateSettings()); } + protected abstract OnlinePlayComposite CreateSettings(); + protected override void PopIn() { Settings.MoveToY(0, TRANSITION_DURATION, Easing.OutQuint); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 2e180f31fd..81ee580a24 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -27,16 +27,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public class MultiplayerMatchSettingsOverlay : MatchSettingsOverlay { - [BackgroundDependencyLoader] - private void load() - { - Child = Settings = new MatchSettings + protected override OnlinePlayComposite CreateSettings() + => new MatchSettings { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Y, SettingsApplied = Hide }; - } protected class MatchSettings : OnlinePlayComposite { diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs index 5eb2b545cb..88ac5ef6e5 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs @@ -26,16 +26,13 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public Action EditPlaylist; - [BackgroundDependencyLoader] - private void load() - { - Child = Settings = new MatchSettings + protected override OnlinePlayComposite CreateSettings() + => new MatchSettings { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Y, EditPlaylist = () => EditPlaylist?.Invoke() }; - } protected class MatchSettings : OnlinePlayComposite { From 4fd6f2101c2d890372fc0c096d09df5c39284b6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 16:28:48 +0900 Subject: [PATCH 1454/1621] Add password textbox input --- osu.Game/Online/Rooms/Room.cs | 1 - .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 9270c4b69d..bccb6f854c 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -146,7 +146,6 @@ namespace osu.Game.Online.Rooms ChannelId.Value = other.ChannelId.Value; Status.Value = other.Status.Value; - Password.Value = other.Password.Value; Availability.Value = other.Availability.Value; Type.Value = other.Type.Value; MaxParticipants.Value = other.MaxParticipants.Value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 81ee580a24..bc2f263a68 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -44,6 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public OsuTextBox NameField, MaxParticipantsField; public RoomAvailabilityPicker AvailabilityPicker; public GameTypePicker TypePicker; + public OsuTextBox PasswordTextBox; public TriangleButton ApplyButton; public OsuSpriteText ErrorText; @@ -190,12 +191,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }, new Section("Password (optional)") { - Alpha = disabled_alpha, - Child = new SettingsPasswordTextBox + Child = PasswordTextBox = new SettingsPasswordTextBox { RelativeSizeAxes = Axes.X, TabbableContentContainer = this, - ReadOnly = true, }, }, } @@ -317,6 +316,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match currentRoom.Value.Name.Value = NameField.Text; currentRoom.Value.Availability.Value = AvailabilityPicker.Current.Value; currentRoom.Value.Type.Value = TypePicker.Current.Value; + currentRoom.Value.Password.Value = PasswordTextBox.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) currentRoom.Value.MaxParticipants.Value = max; From 6a74fde0824fe127c614a06ef5b8afcc7c105f71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Jul 2021 18:53:13 +0900 Subject: [PATCH 1455/1621] Add `has_password` flag and region post only parameters --- osu.Game/Online/Rooms/Room.cs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index bccb6f854c..d1701d544e 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -35,9 +35,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("channel_id")] public readonly Bindable ChannelId = new Bindable(); - [JsonProperty("password")] - public readonly Bindable Password = new Bindable(); - [Cached] [JsonIgnore] public readonly Bindable Category = new Bindable(); @@ -51,10 +48,6 @@ namespace osu.Game.Online.Rooms set => Category.Value = value; } - [Cached] - [JsonIgnore] - public readonly Bindable Duration = new Bindable(); - [Cached] [JsonIgnore] public readonly Bindable MaxAttempts = new Bindable(); @@ -79,6 +72,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("current_user_score")] public readonly Bindable UserScore = new Bindable(); + [JsonProperty("has_password")] + public readonly BindableBool HasPassword = new BindableBool(); + [Cached] [JsonProperty("recent_participants")] public readonly BindableList RecentParticipants = new BindableList(); @@ -87,6 +83,15 @@ namespace osu.Game.Online.Rooms [JsonProperty("participant_count")] public readonly Bindable ParticipantCount = new Bindable(); + #region Properties only used for room creation request + + [JsonProperty("password")] + public readonly Bindable Password = new Bindable(); + + [Cached] + [JsonIgnore] + public readonly Bindable Duration = new Bindable(); + [JsonProperty("duration")] private int? duration { @@ -100,6 +105,8 @@ namespace osu.Game.Online.Rooms } } + #endregion + // Only supports retrieval for now [Cached] [JsonProperty("ends_at")] From 2ca11d458a3ea885ebdae8a033fd0c9eda984317 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 00:53:10 +0900 Subject: [PATCH 1456/1621] Add password to room settings and multiplayer lounge interface --- .../Multiplayer/IMultiplayerLoungeServer.cs | 10 +++++++++ .../Multiplayer/InvalidPasswordException.cs | 22 +++++++++++++++++++ .../Online/Multiplayer/MultiplayerClient.cs | 2 ++ .../Multiplayer/MultiplayerRoomSettings.cs | 5 +++++ 4 files changed, 39 insertions(+) create mode 100644 osu.Game/Online/Multiplayer/InvalidPasswordException.cs diff --git a/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs index 4640640c5f..a04ec53578 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs @@ -15,6 +15,16 @@ namespace osu.Game.Online.Multiplayer /// /// The databased room ID. /// If the user is already in the requested (or another) room. + /// If the room required a password. Task JoinRoom(long roomId); + + /// + /// Request to join a multiplayer room with a provided password. + /// + /// The databased room ID. + /// The password for the join request. + /// If the user is already in the requested (or another) room. + /// If the room provided password was incorrect. + Task JoinRoom(long roomId, string password); } } diff --git a/osu.Game/Online/Multiplayer/InvalidPasswordException.cs b/osu.Game/Online/Multiplayer/InvalidPasswordException.cs new file mode 100644 index 0000000000..0441aea287 --- /dev/null +++ b/osu.Game/Online/Multiplayer/InvalidPasswordException.cs @@ -0,0 +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; +using System.Runtime.Serialization; +using Microsoft.AspNetCore.SignalR; + +namespace osu.Game.Online.Multiplayer +{ + [Serializable] + public class InvalidPasswordException : HubException + { + public InvalidPasswordException() + { + } + + protected InvalidPasswordException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 2e65f7cf1c..a9f4dc9e2f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -212,6 +212,8 @@ namespace osu.Game.Online.Multiplayer return ChangeSettings(new MultiplayerRoomSettings { Name = name.GetOr(Room.Settings.Name), + // TODO: add changing support + Password = Room.Settings.Password, BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID, BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash, RulesetID = item.GetOr(existingPlaylistItem).RulesetID, diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index ee72df4c10..4e94c5982f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -36,12 +36,16 @@ namespace osu.Game.Online.Multiplayer [Key(6)] public long PlaylistItemId { get; set; } + [Key(7)] + public string Password { get; set; } = string.Empty; + public bool Equals(MultiplayerRoomSettings other) => BeatmapID == other.BeatmapID && BeatmapChecksum == other.BeatmapChecksum && RequiredMods.SequenceEqual(other.RequiredMods) && AllowedMods.SequenceEqual(other.AllowedMods) && RulesetID == other.RulesetID + && Password.Equals(other.Password, StringComparison.Ordinal) && Name.Equals(other.Name, StringComparison.Ordinal) && PlaylistItemId == other.PlaylistItemId; @@ -49,6 +53,7 @@ namespace osu.Game.Online.Multiplayer + $" Beatmap:{BeatmapID} ({BeatmapChecksum})" + $" RequiredMods:{string.Join(',', RequiredMods)}" + $" AllowedMods:{string.Join(',', AllowedMods)}" + + $" Password:{(string.IsNullOrEmpty(Password) ? "no" : "yes")}" + $" Ruleset:{RulesetID}" + $" Item:{PlaylistItemId}"; } From 5148069efe501424d788993f7dc719470e551b7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 16:01:45 +0900 Subject: [PATCH 1457/1621] Update signatures in line with no-overload methods (unsupported by signalr) --- osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs | 2 +- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 3 ++- osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 4 ++-- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 5 +++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs index a04ec53578..0a618c8f5c 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs @@ -25,6 +25,6 @@ namespace osu.Game.Online.Multiplayer /// The password for the join request. /// If the user is already in the requested (or another) room. /// If the room provided password was incorrect. - Task JoinRoom(long roomId, string password); + Task JoinRoomWithPassword(long roomId, string password); } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index a9f4dc9e2f..543b3cd752 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -152,8 +152,9 @@ namespace osu.Game.Online.Multiplayer /// Joins the with a given ID. /// /// The room ID. + /// An optional password to use when joining the room. /// The joined . - protected abstract Task JoinRoom(long roomId); + protected abstract Task JoinRoom(long roomId, string? password = null); public Task LeaveRoom() { diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index cf1e18e059..726e26ebe1 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -62,12 +62,12 @@ namespace osu.Game.Online.Multiplayer } } - protected override Task JoinRoom(long roomId) + protected override Task JoinRoom(long roomId, string? password = null) { if (!IsConnected.Value) return Task.FromCanceled(new CancellationToken(true)); - return connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoom), roomId); + return connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty); } protected override Task LeaveRoomInternal() diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index b0c8d6d19b..adc632a2b1 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(userId, newBeatmapAvailability); } - protected override Task JoinRoom(long roomId) + protected override Task JoinRoom(long roomId, string? password = null) { var apiRoom = roomManager.Rooms.Single(r => r.RoomID.Value == roomId); @@ -134,7 +134,8 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapChecksum = apiRoom.Playlist.Last().Beatmap.Value.MD5Hash, RequiredMods = apiRoom.Playlist.Last().RequiredMods.Select(m => new APIMod(m)).ToArray(), AllowedMods = apiRoom.Playlist.Last().AllowedMods.Select(m => new APIMod(m)).ToArray(), - PlaylistItemId = apiRoom.Playlist.Last().ID + PlaylistItemId = apiRoom.Playlist.Last().ID, + Password = password ?? string.Empty, }, Users = { localUser }, Host = localUser From 84b0a3290ce2ff6767308797f46e76770d020204 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 16:01:52 +0900 Subject: [PATCH 1458/1621] Add multiplayer lounge test coverage --- .../TestSceneMultiplayerLoungeSubScreen.cs | 44 +++++++++++++++++++ .../Visual/OnlinePlay/BasicTestRoomManager.cs | 5 ++- 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs new file mode 100644 index 0000000000..0d90b75be7 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.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 System.Linq; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Screens.OnlinePlay.Lounge; +using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMultiplayerLoungeSubScreen : OnlinePlayTestScene + { + protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager; + + private LoungeSubScreen loungeScreen; + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("push screen", () => LoadScreen(loungeScreen = new MultiplayerLoungeSubScreen())); + + AddUntilStep("wait for present", () => loungeScreen.IsCurrentScreen()); + } + + [Test] + public void TestJoinRoomWithoutPassword() + { + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: false)); + } + + [Test] + public void TestJoinRoomWithPassword() + { + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + } + + private RoomsContainer roomsContainer => loungeScreen.ChildrenOfType().First(); + } +} diff --git a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs index 813e617ac5..f2ca3d6357 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay { } - public void AddRooms(int count, RulesetInfo ruleset = null) + public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false) { for (int i = 0; i < count; i++) { @@ -53,7 +53,8 @@ namespace osu.Game.Tests.Visual.OnlinePlay Name = { Value = $"Room {i}" }, Host = { Value = new User { Username = "Host" } }, EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, - Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } + Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }, + Password = { Value = withPassword ? "password" : string.Empty } }; if (ruleset != null) From 08c40938db22e20a6ece17b1e26776a8664629e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Jul 2021 13:50:52 +0900 Subject: [PATCH 1459/1621] Add support for updating a room's password --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 8 ++++---- .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 543b3cd752..052489ca97 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -127,7 +127,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(room.RoomID.Value != null); // Join the server-side room. - var joinedRoom = await JoinRoom(room.RoomID.Value.Value).ConfigureAwait(false); + var joinedRoom = await JoinRoom(room.RoomID.Value.Value, room.Password.Value).ConfigureAwait(false); Debug.Assert(joinedRoom != null); // Populate users. @@ -190,8 +190,9 @@ namespace osu.Game.Online.Multiplayer /// A room must be joined for this to have any effect. /// /// The new room name, if any. + /// The new password, if any. /// The new room playlist item, if any. - public Task ChangeSettings(Optional name = default, Optional item = default) + public Task ChangeSettings(Optional name = default, Optional password = default, Optional item = default) { if (Room == null) throw new InvalidOperationException("Must be joined to a match to change settings."); @@ -213,8 +214,7 @@ namespace osu.Game.Online.Multiplayer return ChangeSettings(new MultiplayerRoomSettings { Name = name.GetOr(Room.Settings.Name), - // TODO: add changing support - Password = Room.Settings.Password, + Password = password.GetOr(Room.Settings.Password), BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID, BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash, RulesetID = item.GetOr(existingPlaylistItem).RulesetID, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index bc2f263a68..fceb124e0a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -303,7 +303,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match // Otherwise, update the room directly in preparation for it to be submitted to the API on match creation. if (client.Room != null) { - client.ChangeSettings(name: NameField.Text).ContinueWith(t => Schedule(() => + client.ChangeSettings(name: NameField.Text, password: PasswordTextBox.Text).ContinueWith(t => Schedule(() => { if (t.IsCompletedSuccessfully) onSuccess(currentRoom.Value); From f35d55c32f3805fe87e54b5c094c25be4975d154 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Jul 2021 14:14:22 +0900 Subject: [PATCH 1460/1621] Fix `HasPassword` not being in sync with `Password` value for client-side rooms --- osu.Game/Online/Rooms/Room.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index d1701d544e..416dc7e5c0 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -126,6 +126,11 @@ namespace osu.Game.Online.Rooms [JsonIgnore] public readonly Bindable Position = new Bindable(-1); + public Room() + { + Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue)); + } + /// /// Create a copy of this room without online information. /// Should be used to create a local copy of a room for submitting in the future. From 242982730fb5fca4c029bb1ca3e23cf59e222121 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Jul 2021 16:52:57 +0900 Subject: [PATCH 1461/1621] Fix incorrect DifficultyBindable binding implementation --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 29 ++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index 1f76a3e366..cf86e9ad19 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -71,6 +71,12 @@ namespace osu.Game.Rulesets.Mods } public DifficultyBindable() + : this(null) + { + } + + public DifficultyBindable(float? defaultValue = null) + : base(defaultValue) { ExtendedLimits.BindValueChanged(_ => updateMaxValue()); } @@ -93,15 +99,22 @@ namespace osu.Game.Rulesets.Mods CurrentNumber.MaxValue = ExtendedLimits.Value && extendedMaxValue != null ? extendedMaxValue.Value : maxValue; } - public new DifficultyBindable GetBoundCopy() => new DifficultyBindable + public override void BindTo(Bindable them) { - BindTarget = this, - CurrentNumber = { BindTarget = CurrentNumber }, - ExtendedLimits = { BindTarget = ExtendedLimits }, - ReadCurrentFromDifficulty = ReadCurrentFromDifficulty, + if (!(them is DifficultyBindable otherDifficultyBindable)) + throw new InvalidOperationException($"Cannot bind to a non-{nameof(DifficultyBindable)}."); + + base.BindTo(them); + + CurrentNumber.BindTarget = otherDifficultyBindable.CurrentNumber; + ExtendedLimits.BindTarget = otherDifficultyBindable.ExtendedLimits; + ReadCurrentFromDifficulty = otherDifficultyBindable.ReadCurrentFromDifficulty; + // the following is only safe as long as these values are effectively constants. - MaxValue = maxValue, - ExtendedMaxValue = extendedMaxValue - }; + MaxValue = otherDifficultyBindable.maxValue; + ExtendedMaxValue = otherDifficultyBindable.extendedMaxValue; + } + + public new DifficultyBindable GetBoundCopy() => new DifficultyBindable { BindTarget = this }; } } From 4b393209ec2715fb57ce3750e8fab11e572cb8d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Jul 2021 17:33:29 +0900 Subject: [PATCH 1462/1621] Implement UnbindFrom() --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index cf86e9ad19..ff859de30b 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -115,6 +115,17 @@ namespace osu.Game.Rulesets.Mods ExtendedMaxValue = otherDifficultyBindable.extendedMaxValue; } + public override void UnbindFrom(IUnbindable them) + { + if (!(them is DifficultyBindable otherDifficultyBindable)) + throw new InvalidOperationException($"Cannot unbind from a non-{nameof(DifficultyBindable)}."); + + base.UnbindFrom(them); + + CurrentNumber.UnbindFrom(otherDifficultyBindable.CurrentNumber); + ExtendedLimits.UnbindFrom(otherDifficultyBindable.ExtendedLimits); + } + public new DifficultyBindable GetBoundCopy() => new DifficultyBindable { BindTarget = this }; } } From 78c74e97d1d2e33735511caf475e0a3d9b34efc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 18:08:19 +0900 Subject: [PATCH 1463/1621] Change to alternative formatting --- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 8cff790e80..449401c0bf 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -31,7 +31,11 @@ namespace osu.Game.Tests.Visual.SongSelect private readonly TestBeatmapDifficultyCache testDifficultyCache = new TestBeatmapDifficultyCache(); [Test] - public void TestLocal([Values("Beatmap", "Some long title and stuff")] string title, [Values("Trial", "Some1's very hardest difficulty")] string version) + public void TestLocal( + [Values("Beatmap", "Some long title and stuff")] + string title, + [Values("Trial", "Some1's very hardest difficulty")] + string version) { showMetadataForBeatmap(() => CreateWorkingBeatmap(new Beatmap { From 3c49b46c5fdfedd20691c307a3797529b3c18ed6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Jul 2021 14:14:58 +0900 Subject: [PATCH 1464/1621] Add lock overlay for rooms which are password protected --- .../TestSceneLoungeRoomsContainer.cs | 6 +++ .../Lounge/Components/DrawableRoom.cs | 43 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 75cc687ee8..798748b4df 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -123,6 +123,12 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3); } + [Test] + public void TestPasswordProtectedRooms() + { + AddStep("add rooms", () => RoomManager.AddRooms(3, withPassword: true)); + } + private bool checkRoomSelected(Room room) => SelectedRoom.Value == room; private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 35782c6104..6fec872a11 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -46,6 +47,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components [Resolved] private BeatmapManager beatmaps { get; set; } + private Container content; + public readonly Room Room; private SelectionState state; @@ -124,7 +127,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(SELECTION_BORDER_WIDTH), - Child = new Container + Child = content = new Container { RelativeSizeAxes = Axes.Both, Masking = true, @@ -204,6 +207,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components }, }, }; + + if (Room.HasPassword.Value) + { + content.Add(new PasswordProtectedIcon()); + } } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -245,5 +253,38 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components parentScreen?.OpenNewRoom(Room.CreateCopy()); }) }; + + private class PasswordProtectedIcon : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + + Size = new Vector2(32); + + InternalChildren = new Drawable[] + { + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopCentre, + Colour = colours.Gray5, + Rotation = 45, + RelativeSizeAxes = Axes.Both, + Width = 2, + }, + new SpriteIcon + { + Icon = FontAwesome.Solid.Lock, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding(6), + Size = new Vector2(14), + } + }; + } + } } } From 9f9d7f9125c61fde650d6dff79c031da148733d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Jul 2021 16:08:12 +0900 Subject: [PATCH 1465/1621] Add remaining pieces of password flow (for osu-web join request) --- .../TestScenePlaylistsMatchSettingsOverlay.cs | 2 +- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 5 +++-- osu.Game/Online/Rooms/JoinRoomRequest.cs | 2 +- osu.Game/Screens/OnlinePlay/Components/RoomManager.cs | 5 ++++- osu.Game/Screens/OnlinePlay/IRoomManager.cs | 7 +++++-- osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 4 ++-- .../OnlinePlay/Multiplayer/MultiplayerRoomManager.cs | 10 +++++----- .../Tests/Visual/OnlinePlay/BasicTestRoomManager.cs | 2 +- 8 files changed, 22 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index a320cb240f..cdc655500d 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -152,7 +152,7 @@ namespace osu.Game.Tests.Visual.Playlists onSuccess?.Invoke(room); } - public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => throw new NotImplementedException(); + public void JoinRoom(Room room, string password, Action onSuccess = null, Action onError = null) => throw new NotImplementedException(); public void PartRoom() => throw new NotImplementedException(); } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 052489ca97..42d436ef11 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -115,7 +115,8 @@ namespace osu.Game.Online.Multiplayer /// Joins the for a given API . /// /// The API . - public async Task JoinRoom(Room room) + /// An optional password to use for the join operation. + public async Task JoinRoom(Room room, string? password = null) { var cancellationSource = joinCancellationSource = new CancellationTokenSource(); @@ -127,7 +128,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(room.RoomID.Value != null); // Join the server-side room. - var joinedRoom = await JoinRoom(room.RoomID.Value.Value, room.Password.Value).ConfigureAwait(false); + var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false); Debug.Assert(joinedRoom != null); // Populate users. diff --git a/osu.Game/Online/Rooms/JoinRoomRequest.cs b/osu.Game/Online/Rooms/JoinRoomRequest.cs index faa20a3e6c..a82dc5a859 100644 --- a/osu.Game/Online/Rooms/JoinRoomRequest.cs +++ b/osu.Game/Online/Rooms/JoinRoomRequest.cs @@ -23,6 +23,6 @@ namespace osu.Game.Online.Rooms return req; } - protected override string Target => $"rooms/{room.RoomID.Value}/users/{User.Id}"; + protected override string Target => $"rooms/{room.RoomID.Value}/users/{User.Id}?password={room.Password.Value}"; } } diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 227a772b2d..da02f9624f 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -84,8 +84,11 @@ namespace osu.Game.Screens.OnlinePlay.Components private JoinRoomRequest currentJoinRoomRequest; - public virtual void JoinRoom(Room room, Action onSuccess = null, Action onError = null) + public virtual void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null) { + // todo: send into JoinRoomRequest directly? + room.Password.Value = password; + currentJoinRoomRequest?.Cancel(); currentJoinRoomRequest = new JoinRoomRequest(room); diff --git a/osu.Game/Screens/OnlinePlay/IRoomManager.cs b/osu.Game/Screens/OnlinePlay/IRoomManager.cs index 8ff02536f3..34c1393ff1 100644 --- a/osu.Game/Screens/OnlinePlay/IRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/IRoomManager.cs @@ -6,6 +6,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Online.Rooms; +#nullable enable + namespace osu.Game.Screens.OnlinePlay { [Cached(typeof(IRoomManager))] @@ -32,15 +34,16 @@ namespace osu.Game.Screens.OnlinePlay /// The to create. /// An action to be invoked if the creation succeeds. /// An action to be invoked if an error occurred. - void CreateRoom(Room room, Action onSuccess = null, Action onError = null); + void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null); /// /// Joins a . /// /// The to join. must be populated. + /// An optional password to use for the join operation. /// /// - void JoinRoom(Room room, Action onSuccess = null, Action onError = null); + void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null); /// /// Parts the currently-joined . diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index f24577a8a5..a8b80655f9 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -167,14 +167,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge filter.HoldFocus = false; } - private void joinRequested(Room room) + public void Join(Room room, string password) { if (joiningRoomOperation != null) return; joiningRoomOperation = ongoingOperationTracker?.BeginOperation(); - RoomManager?.JoinRoom(room, r => + RoomManager?.JoinRoom(room, password, r => { Open(room); joiningRoomOperation?.Dispose(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs index 8526196902..cbba4babe5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs @@ -38,9 +38,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } public override void CreateRoom(Room room, Action onSuccess = null, Action onError = null) - => base.CreateRoom(room, r => joinMultiplayerRoom(r, onSuccess, onError), onError); + => base.CreateRoom(room, r => joinMultiplayerRoom(r, r.Password.Value, onSuccess, onError), onError); - public override void JoinRoom(Room room, Action onSuccess = null, Action onError = null) + public override void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null) { if (!multiplayerClient.IsConnected.Value) { @@ -56,7 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } - base.JoinRoom(room, r => joinMultiplayerRoom(r, onSuccess, onError), onError); + base.JoinRoom(room, password, r => joinMultiplayerRoom(r, password, onSuccess, onError), onError); } public override void PartRoom() @@ -79,11 +79,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }); } - private void joinMultiplayerRoom(Room room, Action onSuccess = null, Action onError = null) + private void joinMultiplayerRoom(Room room, string password, Action onSuccess = null, Action onError = null) { Debug.Assert(room.RoomID.Value != null); - multiplayerClient.JoinRoom(room).ContinueWith(t => + multiplayerClient.JoinRoom(room, password).ContinueWith(t => { if (t.IsCompletedSuccessfully) Schedule(() => onSuccess?.Invoke(room)); diff --git a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs index f2ca3d6357..5d3fa34ec5 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay onSuccess?.Invoke(room); } - public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); + public void JoinRoom(Room room, string password, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); public void PartRoom() { From e25b3518dc0988149a48ac4b8a91cddc644606c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Jul 2021 10:13:57 +0900 Subject: [PATCH 1466/1621] Make password popover display inside `RoomsContainer` rooms --- .../Lounge/Components/DrawableRoom.cs | 100 +++++++++++++++++- .../Lounge/Components/RoomsContainer.cs | 49 +++------ .../OnlinePlay/Lounge/LoungeSubScreen.cs | 2 +- 3 files changed, 114 insertions(+), 37 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 6fec872a11..bc860772b7 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -14,12 +14,15 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osuTK; @@ -27,7 +30,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public class DrawableRoom : OsuClickableContainer, IStateful, IFilterable, IHasContextMenu + public class DrawableRoom : OsuClickableContainer, IStateful, IFilterable, IHasContextMenu, IHasPopover, IKeyBindingHandler { public const float SELECTION_BORDER_WIDTH = 4; private const float corner_radius = 5; @@ -47,6 +50,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components [Resolved] private BeatmapManager beatmaps { get; set; } + [Resolved] + private Bindable selectedRoom { get; set; } + + [Resolved(canBeNull: true)] + private LoungeSubScreen lounge { get; set; } + private Container content; public readonly Room Room; @@ -232,6 +241,22 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Alpha = 0; } + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.Select: + // TODO: this needs to be able to show the popover on demand. + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } + protected override bool ShouldBeConsideredForInput(Drawable child) => state == SelectionState.Selected; private class RoomName : OsuSpriteText @@ -286,5 +311,78 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components }; } } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (selectedRoom.Value != Room) + return true; + + return base.OnMouseDown(e); + } + + protected override bool OnClick(ClickEvent e) + { + if (Room != selectedRoom.Value) + { + selectedRoom.Value = Room; + return true; + } + + if (Room.HasPassword.Value) + // we want our popover to show. this is a bit of a hack. + return false; + + lounge?.Join(Room, null); + + return base.OnClick(e); + } + + public Popover GetPopover() => new PasswordEntryPopover(Room); + + public class PasswordEntryPopover : Popover + { + [Resolved(canBeNull: true)] + private LoungeSubScreen lounge { get; set; } + + public PasswordEntryPopover(Room room) + { + OsuPasswordTextBox passwordTextbox; + + Child = new Container + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.OliveDrab, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Margin = new MarginPadding(10), + Spacing = new Vector2(5), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + passwordTextbox = new OsuPasswordTextBox + { + Width = 200, + }, + new TriangleButton + { + Width = 80, + Text = "Join Room", + Action = () => lounge?.Join(room, passwordTextbox.Text) + } + } + }, + } + }; + } + + protected override Drawable CreateArrow() => Drawable.Empty(); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 5f135a3e90..5a9721a8e3 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Threading; @@ -24,8 +25,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public class RoomsContainer : CompositeDrawable, IKeyBindingHandler { - public Action JoinRequested; - private readonly IBindableList rooms = new BindableList(); private readonly FillFlowContainer roomFlow; @@ -51,16 +50,21 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = new OsuContextMenuContainer + InternalChild = new PopoverContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Child = roomFlow = new FillFlowContainer + Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(2), + Child = roomFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2), + } } }; } @@ -121,19 +125,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { foreach (var room in rooms) { - roomFlow.Add(new DrawableRoom(room) - { - Action = () => - { - if (room == selectedRoom.Value) - { - joinSelected(); - return; - } - - selectRoom(room); - } - }); + roomFlow.Add(new DrawableRoom(room)); } Filter(filter?.Value); @@ -150,7 +142,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components roomFlow.Remove(toRemove); - selectRoom(null); + selectedRoom.Value = null; } } @@ -160,18 +152,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components roomFlow.SetLayoutPosition(room, room.Room.Position.Value); } - private void selectRoom(Room room) => selectedRoom.Value = room; - - private void joinSelected() - { - if (selectedRoom.Value == null) return; - - JoinRequested?.Invoke(selectedRoom.Value); - } - protected override bool OnClick(ClickEvent e) { - selectRoom(null); + selectedRoom.Value = null; return base.OnClick(e); } @@ -181,10 +164,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { switch (action) { - case GlobalAction.Select: - joinSelected(); - return true; - case GlobalAction.SelectNext: beginRepeatSelection(() => selectNext(1), action); return true; @@ -253,7 +232,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components // we already have a valid selection only change selection if we still have a room to switch to. if (room != null) - selectRoom(room); + selectedRoom.Value = room; } #endregion diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index a8b80655f9..11298037d0 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -70,7 +70,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge RelativeSizeAxes = Axes.Both, ScrollbarOverlapsContent = false, Padding = new MarginPadding(10), - Child = roomsContainer = new RoomsContainer { JoinRequested = joinRequested } + Child = roomsContainer = new RoomsContainer() }, loadingLayer = new LoadingLayer(true), } From a3e0168a46d5f2ff87f08ed465ac94178256b84b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 14:35:08 +0900 Subject: [PATCH 1467/1621] Update tests --- .../TestSceneLoungeRoomsContainer.cs | 20 +------------------ .../TestScenePlaylistsLoungeSubScreen.cs | 2 +- .../Lounge/Components/DrawableRoom.cs | 2 +- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 798748b4df..4d5bf8f225 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -4,13 +4,11 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Graphics; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Visual.OnlinePlay; -using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer @@ -29,7 +27,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 0.5f, - JoinRequested = joinRequested }; }); @@ -43,11 +40,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("has 2 rooms", () => container.Rooms.Count == 2); AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0)); - AddStep("select first room", () => container.Rooms.First().Action?.Invoke()); + AddStep("select first room", () => container.Rooms.First().Click()); AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First())); - - AddStep("join first room", () => container.Rooms.First().Action?.Invoke()); - AddAssert("first room joined", () => RoomManager.Rooms.First().Status.Value is JoinedRoomStatus); } [Test] @@ -66,9 +60,6 @@ namespace osu.Game.Tests.Visual.Multiplayer press(Key.Down); press(Key.Down); AddAssert("last room selected", () => checkRoomSelected(RoomManager.Rooms.Last())); - - press(Key.Enter); - AddAssert("last room joined", () => RoomManager.Rooms.Last().Status.Value is JoinedRoomStatus); } [Test] @@ -130,14 +121,5 @@ namespace osu.Game.Tests.Visual.Multiplayer } private bool checkRoomSelected(Room room) => SelectedRoom.Value == room; - - private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus(); - - private class JoinedRoomStatus : RoomStatus - { - public override string Message => "Joined"; - - public override Color4 GetAppropriateColour(OsuColour colours) => colours.Yellow; - } } } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index b16b61c5c7..7bf161d1d0 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Playlists AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms.First())); - AddStep("select last room", () => roomsContainer.Rooms.Last().Action?.Invoke()); + AddStep("select last room", () => roomsContainer.Rooms.Last().Click()); AddUntilStep("first room is masked", () => !checkRoomVisible(roomsContainer.Rooms.First())); AddUntilStep("last room is not masked", () => checkRoomVisible(roomsContainer.Rooms.Last())); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index bc860772b7..678a97a04c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components [Resolved] private BeatmapManager beatmaps { get; set; } - [Resolved] + [Resolved(canBeNull: true)] private Bindable selectedRoom { get; set; } [Resolved(canBeNull: true)] From b4ca6b6188d90c4db0436a19ec57364b6942ac41 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 14:53:12 +0900 Subject: [PATCH 1468/1621] Update popover logic to take advantage of new explicit popup functionality --- .../Lounge/Components/DrawableRoom.cs | 78 ++++++++++--------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 678a97a04c..1747dc6565 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -241,12 +242,25 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Alpha = 0; } + public Popover GetPopover() => new PasswordEntryPopover(Room); + + public MenuItem[] ContextMenuItems => new MenuItem[] + { + new OsuMenuItem("Create copy", MenuItemType.Standard, () => + { + parentScreen?.OpenNewRoom(Room.CreateCopy()); + }) + }; + public bool OnPressed(GlobalAction action) { + if (selectedRoom.Value != Room) + return false; + switch (action) { case GlobalAction.Select: - // TODO: this needs to be able to show the popover on demand. + Click(); return true; } @@ -259,6 +273,33 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components protected override bool ShouldBeConsideredForInput(Drawable child) => state == SelectionState.Selected; + protected override bool OnMouseDown(MouseDownEvent e) + { + if (selectedRoom.Value != Room) + return true; + + return base.OnMouseDown(e); + } + + protected override bool OnClick(ClickEvent e) + { + if (Room != selectedRoom.Value) + { + selectedRoom.Value = Room; + return true; + } + + if (Room.HasPassword.Value) + { + this.ShowPopover(); + return true; + } + + lounge?.Join(Room, null); + + return base.OnClick(e); + } + private class RoomName : OsuSpriteText { [Resolved(typeof(Room), nameof(Online.Rooms.Room.Name))] @@ -271,14 +312,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } - public MenuItem[] ContextMenuItems => new MenuItem[] - { - new OsuMenuItem("Create copy", MenuItemType.Standard, () => - { - parentScreen?.OpenNewRoom(Room.CreateCopy()); - }) - }; - private class PasswordProtectedIcon : CompositeDrawable { [BackgroundDependencyLoader] @@ -312,33 +345,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } - protected override bool OnMouseDown(MouseDownEvent e) - { - if (selectedRoom.Value != Room) - return true; - - return base.OnMouseDown(e); - } - - protected override bool OnClick(ClickEvent e) - { - if (Room != selectedRoom.Value) - { - selectedRoom.Value = Room; - return true; - } - - if (Room.HasPassword.Value) - // we want our popover to show. this is a bit of a hack. - return false; - - lounge?.Join(Room, null); - - return base.OnClick(e); - } - - public Popover GetPopover() => new PasswordEntryPopover(Room); - public class PasswordEntryPopover : Popover { [Resolved(canBeNull: true)] From 947460c3c54d46900385f3321ecfae0d8c55a855 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 15:49:09 +0900 Subject: [PATCH 1469/1621] Add test flow for joining passworded rooms via UI --- .../TestSceneMultiplayerLoungeSubScreen.cs | 35 ++++++++++++++++++- .../Visual/OnlinePlay/BasicTestRoomManager.cs | 8 ++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index 0d90b75be7..3ba0f4969a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs @@ -3,12 +3,16 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Tests.Visual.OnlinePlay; +using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { @@ -18,6 +22,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private LoungeSubScreen loungeScreen; + private Room lastJoinedRoom; + private string lastJoinedPassword; + public override void SetUpSteps() { base.SetUpSteps(); @@ -25,20 +32,46 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("push screen", () => LoadScreen(loungeScreen = new MultiplayerLoungeSubScreen())); AddUntilStep("wait for present", () => loungeScreen.IsCurrentScreen()); + + AddStep("bind to event", () => + { + lastJoinedRoom = null; + lastJoinedPassword = null; + RoomManager.JoinRoomRequested = onRoomJoined; + }); } [Test] public void TestJoinRoomWithoutPassword() { AddStep("add room", () => RoomManager.AddRooms(1, withPassword: false)); + AddStep("select room", () => InputManager.Key(Key.Down)); + AddStep("join room", () => InputManager.Key(Key.Enter)); + + AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First()); + AddAssert("room join password correct", () => lastJoinedPassword == null); } [Test] public void TestJoinRoomWithPassword() { + DrawableRoom.PasswordEntryPopover passwordEntryPopover = null; + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + AddStep("select room", () => InputManager.Key(Key.Down)); + AddStep("attempt join room", () => InputManager.Key(Key.Enter)); + AddUntilStep("password prompt appeared", () => (passwordEntryPopover = loungeScreen.ChildrenOfType().FirstOrDefault()) != null); + AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password"); + AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().Click()); + + AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First()); + AddAssert("room join password correct", () => lastJoinedPassword == "password"); } - private RoomsContainer roomsContainer => loungeScreen.ChildrenOfType().First(); + private void onRoomJoined(Room room, string password) + { + lastJoinedRoom = room; + lastJoinedPassword = password; + } } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs index 5d3fa34ec5..82c7266598 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs @@ -26,6 +26,8 @@ namespace osu.Game.Tests.Visual.OnlinePlay public readonly BindableList Rooms = new BindableList(); + public Action JoinRoomRequested; + public IBindable InitialRoomsReceived { get; } = new Bindable(true); IBindableList IRoomManager.Rooms => Rooms; @@ -37,7 +39,11 @@ namespace osu.Game.Tests.Visual.OnlinePlay onSuccess?.Invoke(room); } - public void JoinRoom(Room room, string password, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room); + public void JoinRoom(Room room, string password, Action onSuccess = null, Action onError = null) + { + JoinRoomRequested?.Invoke(room, password); + onSuccess?.Invoke(room); + } public void PartRoom() { From 413f8adb36e36115deb020160acee13b8a1e6ec7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 15:53:28 +0900 Subject: [PATCH 1470/1621] 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 9280eaf97c..f9ec8dd099 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 8a3c69e40c..8f9a57167f 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 2eea646c61..c6e52b8dd5 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From bbc3a013c888596a8e4501033c9bd813112735bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 18:29:50 +0900 Subject: [PATCH 1471/1621] Use `BasicPopover` for now --- .../Lounge/Components/DrawableRoom.cs | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 1747dc6565..a95e0c2b16 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -345,50 +345,44 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } - public class PasswordEntryPopover : Popover + public class PasswordEntryPopover : BasicPopover { + private readonly Room room; + [Resolved(canBeNull: true)] private LoungeSubScreen lounge { get; set; } public PasswordEntryPopover(Room room) + { + this.room = room; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) { OsuPasswordTextBox passwordTextbox; - Child = new Container + Child = new FillFlowContainer { + Margin = new MarginPadding(10), + Spacing = new Vector2(5), AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, Children = new Drawable[] { - new Box + passwordTextbox = new OsuPasswordTextBox { - Colour = Color4.OliveDrab, - RelativeSizeAxes = Axes.Both, + Width = 200, }, - new FillFlowContainer + new TriangleButton { - Margin = new MarginPadding(10), - Spacing = new Vector2(5), - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - passwordTextbox = new OsuPasswordTextBox - { - Width = 200, - }, - new TriangleButton - { - Width = 80, - Text = "Join Room", - Action = () => lounge?.Join(room, passwordTextbox.Text) - } - } - }, + Width = 80, + Text = "Join Room", + Action = () => lounge?.Join(room, passwordTextbox.Text) + } } }; } - - protected override Drawable CreateArrow() => Drawable.Empty(); } } } From c5319c06c2e29ddc1f1903ad48b1ecd7e832224b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 18:54:07 +0900 Subject: [PATCH 1472/1621] Add password attributes to `CopyFrom` to make testing work better --- osu.Game/Online/Rooms/Room.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 416dc7e5c0..f79a410bd9 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -159,6 +159,8 @@ namespace osu.Game.Online.Rooms ChannelId.Value = other.ChannelId.Value; Status.Value = other.Status.Value; Availability.Value = other.Availability.Value; + Password.Value = other.Password.Value; + HasPassword.Value = other.HasPassword.Value; Type.Value = other.Type.Value; MaxParticipants.Value = other.MaxParticipants.Value; ParticipantCount.Value = other.ParticipantCount.Value; From 4dea2d97782151a1a8e383cd09cd225662b99d15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 18:54:17 +0900 Subject: [PATCH 1473/1621] Dismiss popovers on returning to lounge --- .../Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 7 +++++++ osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 5a9721a8e3..8ab80d947c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -7,6 +7,7 @@ using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -244,5 +245,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components if (roomManager != null) roomManager.RoomsUpdated -= updateSorting; } + + public void HideAnyPopovers() + { + // must be called on a child of the PopoverContainer due to parent traversal not considering self. + roomFlow.HidePopover(); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 11298037d0..dd6106b868 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -46,10 +46,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge [CanBeNull] private IDisposable joiningRoomOperation { get; set; } + private RoomsContainer roomsContainer; + [BackgroundDependencyLoader] private void load() { - RoomsContainer roomsContainer; OsuScrollContainer scrollContainer; InternalChildren = new Drawable[] @@ -165,6 +166,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { base.OnSuspending(next); filter.HoldFocus = false; + + // ensure any password prompt is dismissed. + roomsContainer.HideAnyPopovers(); } public void Join(Room room, string password) From c6bd58ea4bf0cecfaa3456038ee29a928b801a46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 19:20:39 +0900 Subject: [PATCH 1474/1621] Exit match sub screen when a room goes away Closes #13847. I think we can probably get some test coverage of this if required, but needs a bit of thought (basically an error needs to be thrown during the multiplayer client portion of the join procedure, after `CurrentRoom` is non-null but before the join completes). Manual testing on password branch (#13861) is possible since it currently errors due to missing method on the live/dev servers. - Create game, which will fail with `MethodNotExists`. - Note the fields on the settings screen are emptied. - Fill fields again and press create game (crash). --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4b8c4422ec..4f5d54e203 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -48,6 +48,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } + [Resolved] + private Bindable currentRoom { get; set; } + private MultiplayerMatchSettingsOverlay settingsOverlay; private readonly IBindable isConnected = new Bindable(); @@ -273,6 +276,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!connected.NewValue) Schedule(this.Exit); }, true); + + currentRoom.BindValueChanged(room => + { + if (room.NewValue == null) + { + // the room has gone away. + // this could mean something happened during the join process, or an external connection issue occurred. + Schedule(this.Exit); + } + }, true); } protected override void UpdateMods() From 1deaefacb7c7236c20a5526db5ffb75a58bbc7d8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 17:53:15 +0300 Subject: [PATCH 1475/1621] Add "basic" lime colour theme --- osu.Game/Overlays/OverlayColourProvider.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index abd1e43f25..4c698c2af7 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -14,6 +14,7 @@ namespace osu.Game.Overlays public static OverlayColourProvider Red { get; } = new OverlayColourProvider(OverlayColourScheme.Red); public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink); public static OverlayColourProvider Orange { get; } = new OverlayColourProvider(OverlayColourScheme.Orange); + public static OverlayColourProvider Lime { get; } = new OverlayColourProvider(OverlayColourScheme.Lime); public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green); public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple); public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue); @@ -68,6 +69,9 @@ namespace osu.Game.Overlays case OverlayColourScheme.Orange: return 46 / 360f; + case OverlayColourScheme.Lime: + return 90 / 360f; + case OverlayColourScheme.Green: return 115 / 360f; @@ -85,6 +89,7 @@ namespace osu.Game.Overlays Red, Pink, Orange, + Lime, Green, Purple, Blue From 9869986c59482f4c64b718e1bceb17d0ebc5d9aa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 17:55:29 +0300 Subject: [PATCH 1476/1621] Remove duplicated colour definitions --- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 3 ++- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 3 ++- osu.Game/Graphics/OsuColour.cs | 4 ---- osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs | 5 ++--- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 8a6cfaf688..b871f8bcc7 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -3,6 +3,7 @@ using osu.Framework.Bindables; using osu.Game.Graphics; +using osu.Game.Overlays; using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints @@ -25,7 +26,7 @@ namespace osu.Game.Beatmaps.ControlPoints MaxValue = 10 }; - public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1; + public override Color4 GetRepresentingColour(OsuColour colours) => OverlayColourProvider.Lime.Colour1; /// /// The speed multiplier at this control point. diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index ec20328fab..22808671b0 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -4,6 +4,7 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; +using osu.Game.Overlays; using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints @@ -20,7 +21,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// private const double default_beat_length = 60000.0 / 60.0; - public override Color4 GetRepresentingColour(OsuColour colours) => colours.Orange1; + public override Color4 GetRepresentingColour(OsuColour colours) => OverlayColourProvider.Orange.Colour1; public static readonly TimingControlPoint DEFAULT = new TimingControlPoint { diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index a44c28eaa6..3533ab177c 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -198,10 +198,6 @@ namespace osu.Game.Graphics public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee"); public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff"); - // in latest editor design logic, need to figure out where these sit... - public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66"); - public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966"); - // Content Background public readonly Color4 B5 = Color4Extensions.FromHex(@"222a28"); diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs index 421806eea8..91d6460ea6 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; namespace osu.Game.Overlays.Wiki.Markdown { @@ -66,7 +65,7 @@ namespace osu.Game.Overlays.Wiki.Markdown public string Text { get; set; } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colour) + private void load(OverlayColourProvider colourProvider) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -81,7 +80,7 @@ namespace osu.Game.Overlays.Wiki.Markdown }, textFlow = parentFlowComponent.CreateTextFlow().With(t => { - t.Colour = colour.Orange1; + t.Colour = OverlayColourProvider.Orange.Colour1; t.Padding = new MarginPadding { Vertical = 10, From 143777271164b912107d949b1727324347ebf609 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 18:11:51 +0300 Subject: [PATCH 1477/1621] Update hue of orange colour scheme --- osu.Game/Overlays/OverlayColourProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index abd1e43f25..1f78ffc870 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -66,7 +66,7 @@ namespace osu.Game.Overlays return 333 / 360f; case OverlayColourScheme.Orange: - return 46 / 360f; + return 45 / 360f; case OverlayColourScheme.Green: return 115 / 360f; From c96a76df673e46320147e6ee6b7fe0886df2bc13 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 18:17:13 +0300 Subject: [PATCH 1478/1621] Update specified link --- osu.Game/Overlays/OverlayColourProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 1f78ffc870..ac1cfb15c7 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(colourScheme), saturation, lightness, 1)); - // See https://github.com/ppy/osu-web/blob/4218c288292d7c810b619075471eaea8bbb8f9d8/app/helpers.php#L1463 + // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 private static float getBaseHue(OverlayColourScheme colourScheme) { switch (colourScheme) From b2b966463a87a3e441f8cf18acea6bb3abe07e23 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 19:05:36 +0300 Subject: [PATCH 1479/1621] Update hue of green colour scheme --- osu.Game/Overlays/OverlayColourProvider.cs | 23 +++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index ac1cfb15c7..8ef3ccbf80 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -12,11 +12,12 @@ namespace osu.Game.Overlays private readonly OverlayColourScheme colourScheme; public static OverlayColourProvider Red { get; } = new OverlayColourProvider(OverlayColourScheme.Red); - public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink); + public static OverlayColourProvider DarkOrange { get; } = new OverlayColourProvider(OverlayColourScheme.DarkOrange); public static OverlayColourProvider Orange { get; } = new OverlayColourProvider(OverlayColourScheme.Orange); public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green); - public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple); public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue); + public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple); + public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink); public OverlayColourProvider(OverlayColourScheme colourScheme) { @@ -62,8 +63,8 @@ namespace osu.Game.Overlays case OverlayColourScheme.Red: return 0; - case OverlayColourScheme.Pink: - return 333 / 360f; + case OverlayColourScheme.DarkOrange: + return 20 / 360f; case OverlayColourScheme.Orange: return 45 / 360f; @@ -71,11 +72,17 @@ namespace osu.Game.Overlays case OverlayColourScheme.Green: return 115 / 360f; - case OverlayColourScheme.Purple: - return 255 / 360f; + case OverlayColourScheme.Cyan: + return 160 / 360f; case OverlayColourScheme.Blue: return 200 / 360f; + + case OverlayColourScheme.Purple: + return 255 / 360f; + + case OverlayColourScheme.Pink: + return 333 / 360f; } } } @@ -84,9 +91,11 @@ namespace osu.Game.Overlays { Red, Pink, + DarkOrange, Orange, Green, Purple, - Blue + Blue, + Cyan, } } From 62a00a82de0c184ca525ae639594f26e74d7e18a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 19:11:14 +0300 Subject: [PATCH 1480/1621] Revert completely irrelevant changes This reverts commit b2b966463a87a3e441f8cf18acea6bb3abe07e23. --- osu.Game/Overlays/OverlayColourProvider.cs | 23 +++++++--------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 8ef3ccbf80..ac1cfb15c7 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -12,12 +12,11 @@ namespace osu.Game.Overlays private readonly OverlayColourScheme colourScheme; public static OverlayColourProvider Red { get; } = new OverlayColourProvider(OverlayColourScheme.Red); - public static OverlayColourProvider DarkOrange { get; } = new OverlayColourProvider(OverlayColourScheme.DarkOrange); + public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink); public static OverlayColourProvider Orange { get; } = new OverlayColourProvider(OverlayColourScheme.Orange); public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green); - public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue); public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple); - public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink); + public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue); public OverlayColourProvider(OverlayColourScheme colourScheme) { @@ -63,8 +62,8 @@ namespace osu.Game.Overlays case OverlayColourScheme.Red: return 0; - case OverlayColourScheme.DarkOrange: - return 20 / 360f; + case OverlayColourScheme.Pink: + return 333 / 360f; case OverlayColourScheme.Orange: return 45 / 360f; @@ -72,17 +71,11 @@ namespace osu.Game.Overlays case OverlayColourScheme.Green: return 115 / 360f; - case OverlayColourScheme.Cyan: - return 160 / 360f; - - case OverlayColourScheme.Blue: - return 200 / 360f; - case OverlayColourScheme.Purple: return 255 / 360f; - case OverlayColourScheme.Pink: - return 333 / 360f; + case OverlayColourScheme.Blue: + return 200 / 360f; } } } @@ -91,11 +84,9 @@ namespace osu.Game.Overlays { Red, Pink, - DarkOrange, Orange, Green, Purple, - Blue, - Cyan, + Blue } } From 821c1b5335d5d3a7b8bd3bd89b3a30f255bc320e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Jul 2021 19:11:26 +0300 Subject: [PATCH 1481/1621] Update hue of green colour scheme --- osu.Game/Overlays/OverlayColourProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index ac1cfb15c7..3fb8c6c4cf 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -69,7 +69,7 @@ namespace osu.Game.Overlays return 45 / 360f; case OverlayColourScheme.Green: - return 115 / 360f; + return 125 / 360f; case OverlayColourScheme.Purple: return 255 / 360f; From f3fe472a3313eb9f3406571d57d87cc7ed1559e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Jul 2021 22:25:21 +0200 Subject: [PATCH 1482/1621] Add failing test case for reset to defaults --- .../TestSceneModDifficultyAdjustSettings.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index bf494d4362..e0d76b3e4a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -157,6 +157,23 @@ namespace osu.Game.Tests.Visual.UserInterface checkBindableAtValue("Circle Size", 3); } + [Test] + public void TestResetToDefaults() + { + setBeatmapWithDifficultyParameters(5); + + setSliderValue("Circle Size", 3); + setExtendedLimits(true); + + checkSliderAtValue("Circle Size", 3); + checkBindableAtValue("Circle Size", 3); + + AddStep("reset mod settings", () => modDifficultyAdjust.ResetSettingsToDefaults()); + + checkSliderAtValue("Circle Size", 5); + checkBindableAtValue("Circle Size", null); + } + private void resetToDefault(string name) { AddStep($"Reset {name} to default", () => From cce4a4dc31553b1a8ba2358529a4718fa71523f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Jul 2021 22:25:33 +0200 Subject: [PATCH 1483/1621] Fix incorrect value copy order in `BindTo()` --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index ff859de30b..664b88eef4 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -104,15 +104,17 @@ namespace osu.Game.Rulesets.Mods if (!(them is DifficultyBindable otherDifficultyBindable)) throw new InvalidOperationException($"Cannot bind to a non-{nameof(DifficultyBindable)}."); - base.BindTo(them); - - CurrentNumber.BindTarget = otherDifficultyBindable.CurrentNumber; - ExtendedLimits.BindTarget = otherDifficultyBindable.ExtendedLimits; ReadCurrentFromDifficulty = otherDifficultyBindable.ReadCurrentFromDifficulty; - // the following is only safe as long as these values are effectively constants. + // the following max value copies are only safe as long as these values are effectively constants. MaxValue = otherDifficultyBindable.maxValue; ExtendedMaxValue = otherDifficultyBindable.extendedMaxValue; + + ExtendedLimits.BindTarget = otherDifficultyBindable.ExtendedLimits; + + // the actual values need to be copied after the max value constraints. + CurrentNumber.BindTarget = otherDifficultyBindable.CurrentNumber; + base.BindTo(them); } public override void UnbindFrom(IUnbindable them) From ac15dae93062b2cba9f934d9ae1ed5f7e822abe3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 12:35:25 +0900 Subject: [PATCH 1484/1621] 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 9280eaf97c..d36ae50280 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 8a3c69e40c..cf4918346c 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 2eea646c61..e573ca97e3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 125bd36ab161f683811d93d434ecbbcddc12f6ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 14:27:07 +0900 Subject: [PATCH 1485/1621] Send password in request ctor directly --- osu.Game/Online/Rooms/JoinRoomRequest.cs | 7 +++++-- osu.Game/Screens/OnlinePlay/Components/RoomManager.cs | 5 +---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Rooms/JoinRoomRequest.cs b/osu.Game/Online/Rooms/JoinRoomRequest.cs index a82dc5a859..53bd2a6106 100644 --- a/osu.Game/Online/Rooms/JoinRoomRequest.cs +++ b/osu.Game/Online/Rooms/JoinRoomRequest.cs @@ -10,19 +10,22 @@ namespace osu.Game.Online.Rooms public class JoinRoomRequest : APIRequest { private readonly Room room; + private readonly string password; - public JoinRoomRequest(Room room) + public JoinRoomRequest(Room room, string password) { this.room = room; + this.password = password; } protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); req.Method = HttpMethod.Put; + req.AddParameter("password", password); return req; } - protected override string Target => $"rooms/{room.RoomID.Value}/users/{User.Id}?password={room.Password.Value}"; + protected override string Target => $"rooms/{room.RoomID.Value}/users/{User.Id}"; } } diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index da02f9624f..422576648c 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -86,11 +86,8 @@ namespace osu.Game.Screens.OnlinePlay.Components public virtual void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null) { - // todo: send into JoinRoomRequest directly? - room.Password.Value = password; - currentJoinRoomRequest?.Cancel(); - currentJoinRoomRequest = new JoinRoomRequest(room); + currentJoinRoomRequest = new JoinRoomRequest(room, password); currentJoinRoomRequest.Success += () => { From 5cffaf4d3b591c366209228afd17822b06c73881 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 14:34:56 +0900 Subject: [PATCH 1486/1621] Add extra explanatory comment to avoid any confusion --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4f5d54e203..aa8a59e13b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -283,6 +283,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { // the room has gone away. // this could mean something happened during the join process, or an external connection issue occurred. + // one specific scenario is where the underlying room is created, but the signalr server returns an error during the join process. this triggers a PartRoom operation (see https://github.com/ppy/osu/blob/7654df94f6f37b8382be7dfcb4f674e03bd35427/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs#L97) Schedule(this.Exit); } }, true); From 6409a518dbba46a9f5f30f88f6b560442c9daa9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 16:34:56 +0900 Subject: [PATCH 1487/1621] Focus password text box on popover display --- .../OnlinePlay/Lounge/Components/DrawableRoom.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index a95e0c2b16..b8c4b57378 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -357,11 +357,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components this.room = room; } + private OsuPasswordTextBox passwordTextbox; + [BackgroundDependencyLoader] private void load(OsuColour colours) { - OsuPasswordTextBox passwordTextbox; - Child = new FillFlowContainer { Margin = new MarginPadding(10), @@ -383,6 +383,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + GetContainingInputManager().ChangeFocus(passwordTextbox); + } } } } From 481e4dedb0d7c49e0a2470af5645ceba3cb2d521 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 16:35:49 +0900 Subject: [PATCH 1488/1621] Move `PopoverContainer` to `OsuGameBase` --- osu.Game/OsuGameBase.cs | 7 ++++++- .../OnlinePlay/Lounge/Components/RoomsContainer.cs | 14 ++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5878727ad8..4b5fa4f62e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -13,6 +13,7 @@ using osu.Framework.Development; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -341,7 +342,11 @@ namespace osu.Game globalBindings = new GlobalActionContainer(this) }; - MenuCursorContainer.Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }; + MenuCursorContainer.Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } + }; base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 8ab80d947c..0910ff1a7a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -11,7 +11,6 @@ using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Threading; @@ -51,21 +50,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = new PopoverContainer + InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Child = new OsuContextMenuContainer + Child = roomFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Child = roomFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(2), - } + Direction = FillDirection.Vertical, + Spacing = new Vector2(2), } }; } From 60e17fc2b7b81998f4e2d5c1f30cdb26dc67b4d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 17:12:35 +0900 Subject: [PATCH 1489/1621] Fix disconnected-from-server multiplayer exit sequence being blocked by confirmation dialog --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4b8c4422ec..e1eebb7e41 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -310,7 +310,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public override bool OnExiting(IScreen next) { - if (client.Room == null) + // the room may not be left immediately after a disconnection due to async flow, + // so checking the IsConnected status is also required. + if (client.Room == null || !client.IsConnected.Value) { // room has not been created yet; exit immediately. return base.OnExiting(next); From 28ff92e34ec214f97d70d546ef7c8650ce757f1d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Jul 2021 17:31:28 +0900 Subject: [PATCH 1490/1621] Add test --- .../Multiplayer/TestSceneMultiplayer.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 2bb3129f68..7673efb78f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; @@ -184,6 +185,26 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("play started", () => !multiplayerScreen.IsCurrentScreen()); } + [Test] + public void TestSubScreenExitedWhenDisconnectedFromMultiplayerServer() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + AddStep("disconnect", () => client.Disconnect()); + AddUntilStep("back in lounge", () => this.ChildrenOfType().FirstOrDefault()?.IsCurrentScreen() == true); + } + [Test] public void TestLeaveNavigation() { From 6da2a3d51fe441280af9536bca3fcf02662826ce Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:50:11 +0200 Subject: [PATCH 1491/1621] Add zero-length objects check and tests --- .../Checks/CheckZeroLengthObjectsTest.cs | 94 +++++++++++++++++++ .../Edit/Checks/CheckZeroLengthObjects.cs | 47 ++++++++++ 2 files changed, 141 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckZeroLengthObjectsTest.cs create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckZeroLengthObjects.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckZeroLengthObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckZeroLengthObjectsTest.cs new file mode 100644 index 0000000000..93b20cd166 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckZeroLengthObjectsTest.cs @@ -0,0 +1,94 @@ +// 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.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; +using osuTK; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckZeroLengthObjectsTest + { + private CheckZeroLengthObjects check; + + [SetUp] + public void Setup() + { + check = new CheckZeroLengthObjects(); + } + + [Test] + public void TestCircle() + { + assertOk(new List + { + new HitCircle { StartTime = 1000, Position = new Vector2(0, 0) } + }); + } + + [Test] + public void TestRegularSlider() + { + assertOk(new List + { + getSliderMock(1000).Object + }); + } + + [Test] + public void TestZeroLengthSlider() + { + assertZeroLength(new List + { + getSliderMock(0).Object + }); + } + + [Test] + public void TestNegativeLengthSlider() + { + assertZeroLength(new List + { + getSliderMock(-1000).Object + }); + } + + private Mock getSliderMock(double duration) + { + var mockSlider = new Mock(); + mockSlider.As().Setup(d => d.Duration).Returns(duration); + + return mockSlider; + } + + private void assertOk(List hitObjects) + { + Assert.That(check.Run(getContext(hitObjects)), Is.Empty); + } + + private void assertZeroLength(List hitObjects) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.First().Template is CheckZeroLengthObjects.IssueTemplateZeroLength); + } + + private BeatmapVerifierContext getContext(List hitObjects) + { + var beatmap = new Beatmap { HitObjects = hitObjects }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} diff --git a/osu.Game/Rulesets/Edit/Checks/CheckZeroLengthObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckZeroLengthObjects.cs new file mode 100644 index 0000000000..b9be94736b --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckZeroLengthObjects.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.Collections.Generic; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckZeroLengthObjects : ICheck + { + /// + /// The duration can be this low before being treated as having no length, in case of precision errors. Unit is milliseconds. + /// + private const double leniency = 0.5d; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Zero-length hitobjects"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateZeroLength(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + foreach (var hitObject in context.Beatmap.HitObjects) + { + if (!(hitObject is IHasDuration hasDuration)) + continue; + + if (hasDuration.Duration < leniency) + yield return new IssueTemplateZeroLength(this).Create(hitObject, hasDuration.Duration); + } + } + + public class IssueTemplateZeroLength : IssueTemplate + { + public IssueTemplateZeroLength(ICheck check) + : base(check, IssueType.Problem, "{0} has a duration of {1:0}.") + { + } + + public Issue Create(HitObject hitobject, double duration) => new Issue(hitobject, this, hitobject.GetType(), duration); + } + } +} From fec944830144eefcd2913a0d435ca507959a719f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:50:41 +0200 Subject: [PATCH 1492/1621] Add too short sliders check and tests --- .../Editor/Checks/CheckTooShortSlidersTest.cs | 145 ++++++++++++++++++ .../Edit/Checks/CheckTooShortSliders.cs | 48 ++++++ 2 files changed, 193 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSlidersTest.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSliders.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSlidersTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSlidersTest.cs new file mode 100644 index 0000000000..2eab5a4ce6 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSlidersTest.cs @@ -0,0 +1,145 @@ +// 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.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +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 CheckTooShortSlidersTest + { + private CheckTooShortSliders check; + + [SetUp] + public void Setup() + { + check = new CheckTooShortSliders(); + } + + [Test] + public void TestLongSlider() + { + Slider slider = new Slider + { + StartTime = 0, + RepeatCount = 0, + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(100, 0)) + }) + }; + + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertOk(new List { slider }); + } + + [Test] + public void TestShortSlider() + { + Slider slider = new Slider + { + StartTime = 0, + RepeatCount = 0, + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(25, 0)) + }) + }; + + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertOk(new List { slider }); + } + + [Test] + public void TestTooShortSliderExpert() + { + Slider slider = new Slider + { + StartTime = 0, + RepeatCount = 0, + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(10, 0)) + }) + }; + + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertOk(new List { slider }, DifficultyRating.Expert); + } + + [Test] + public void TestTooShortSlider() + { + Slider slider = new Slider + { + StartTime = 0, + RepeatCount = 0, + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(10, 0)) + }) + }; + + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertTooShort(new List { slider }); + } + + [Test] + public void TestTooShortSliderWithRepeats() + { + // Would be ok if we looked at the duration, but not if we look at the span duration. + Slider slider = new Slider + { + StartTime = 0, + RepeatCount = 2, + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(10, 0)) + }) + }; + + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertTooShort(new List { slider }); + } + + private void assertOk(List hitObjects, DifficultyRating difficultyRating = DifficultyRating.Easy) + { + Assert.That(check.Run(getContext(hitObjects, difficultyRating)), Is.Empty); + } + + private void assertTooShort(List hitObjects, DifficultyRating difficultyRating = DifficultyRating.Easy) + { + var issues = check.Run(getContext(hitObjects, difficultyRating)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.First().Template is CheckTooShortSliders.IssueTemplateTooShort); + } + + private BeatmapVerifierContext getContext(List hitObjects, DifficultyRating difficultyRating) + { + var beatmap = new Beatmap { HitObjects = hitObjects }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSliders.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSliders.cs new file mode 100644 index 0000000000..159498c479 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSliders.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Checks +{ + public class CheckTooShortSliders : ICheck + { + /// + /// The shortest acceptable duration between the head and tail of the slider (so ignoring repeats). + /// + private const double span_duration_threshold = 125; // 240 BPM 1/2 + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Spread, "Too short sliders"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateTooShort(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + if (context.InterpretedDifficulty > DifficultyRating.Easy) + yield break; + + foreach (var hitObject in context.Beatmap.HitObjects) + { + if (hitObject is Slider slider && slider.SpanDuration < span_duration_threshold) + yield return new IssueTemplateTooShort(this).Create(slider); + } + } + + public class IssueTemplateTooShort : IssueTemplate + { + public IssueTemplateTooShort(ICheck check) + : base(check, IssueType.Problem, "This slider is too short ({0:0} ms), expected at least {1:0} ms.") + { + } + + public Issue Create(Slider slider) => new Issue(slider, this, slider.SpanDuration, span_duration_threshold); + } + } +} From 53c0298b5ea9689eba4eb33dfc29f43224e63e50 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:51:40 +0200 Subject: [PATCH 1493/1621] Add too short spinners check and tests --- .../Checks/CheckTooShortSpinnersTest.cs | 116 ++++++++++++++++++ .../Edit/Checks/CheckTooShortSpinners.cs | 61 +++++++++ 2 files changed, 177 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs new file mode 100644 index 0000000000..6a3f168ee1 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs @@ -0,0 +1,116 @@ +// 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.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Edit.Checks; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks +{ + [TestFixture] + public class CheckTooShortSpinnersTest + { + private CheckTooShortSpinners check; + private BeatmapDifficulty difficulty; + + [SetUp] + public void Setup() + { + check = new CheckTooShortSpinners(); + difficulty = new BeatmapDifficulty(); + } + + [Test] + public void TestLongSpinner() + { + Spinner spinner = new Spinner { StartTime = 0, Duration = 4000 }; + spinner.ApplyDefaults(new ControlPointInfo(), difficulty); + + assertOk(new List { spinner }, difficulty); + } + + [Test] + public void TestShortSpinner() + { + Spinner spinner = new Spinner { StartTime = 0, Duration = 750 }; + spinner.ApplyDefaults(new ControlPointInfo(), difficulty); + + assertOk(new List { spinner }, difficulty); + } + + [Test] + public void TestVeryShortSpinner() + { + // Spinners at a certain duration only get 1000 points if approached by auto at a certain angle, making it difficult to determine. + Spinner spinner = new Spinner { StartTime = 0, Duration = 475 }; + spinner.ApplyDefaults(new ControlPointInfo(), difficulty); + + assertVeryShort(new List { spinner }, difficulty); + } + + [Test] + public void TestTooShortSpinner() + { + Spinner spinner = new Spinner { StartTime = 0, Duration = 400 }; + spinner.ApplyDefaults(new ControlPointInfo(), difficulty); + + assertTooShort(new List { spinner }, difficulty); + } + + [Test] + public void TestTooShortSpinnerVaryingOd() + { + const double duration = 450; + + var difficultyLowOd = new BeatmapDifficulty { OverallDifficulty = 1 }; + Spinner spinnerLowOd = new Spinner { StartTime = 0, Duration = duration }; + spinnerLowOd.ApplyDefaults(new ControlPointInfo(), difficultyLowOd); + + var difficultyHighOd = new BeatmapDifficulty { OverallDifficulty = 10 }; + Spinner spinnerHighOd = new Spinner { StartTime = 0, Duration = duration }; + spinnerHighOd.ApplyDefaults(new ControlPointInfo(), difficultyHighOd); + + assertOk(new List { spinnerLowOd }, difficultyLowOd); + assertTooShort(new List { spinnerHighOd }, difficultyHighOd); + } + + private void assertOk(List hitObjects, BeatmapDifficulty beatmapDifficulty) + { + Assert.That(check.Run(getContext(hitObjects, beatmapDifficulty)), Is.Empty); + } + + private void assertVeryShort(List hitObjects, BeatmapDifficulty beatmapDifficulty) + { + var issues = check.Run(getContext(hitObjects, beatmapDifficulty)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.First().Template is CheckTooShortSpinners.IssueTemplateVeryShort); + } + + private void assertTooShort(List hitObjects, BeatmapDifficulty beatmapDifficulty) + { + var issues = check.Run(getContext(hitObjects, beatmapDifficulty)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.First().Template is CheckTooShortSpinners.IssueTemplateTooShort); + } + + private BeatmapVerifierContext getContext(List hitObjects, BeatmapDifficulty beatmapDifficulty) + { + var beatmap = new Beatmap + { + HitObjects = hitObjects, + BeatmapInfo = new BeatmapInfo { BaseDifficulty = beatmapDifficulty } + }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs new file mode 100644 index 0000000000..0d0c3d9e69 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.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.Collections.Generic; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Checks +{ + public class CheckTooShortSpinners : ICheck + { + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Spread, "Too short spinners"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateTooShort(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + double od = context.Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; + + // These are meant to reflect the duration necessary for auto to score at least 1000 points on the spinner. + // It's difficult to eliminate warnings here, as auto achieving 1000 points depends on the approach angle on some spinners. + double warningThreshold = 500 + (od < 5 ? (5 - od) * -21.8 : (od - 5) * 20); // Anything above this is always ok. + double problemThreshold = 450 + (od < 5 ? (5 - od) * -17 : (od - 5) * 17); // Anything below this is never ok. + + foreach (var hitObject in context.Beatmap.HitObjects) + { + if (!(hitObject is Spinner spinner)) + continue; + + if (spinner.Duration < problemThreshold) + yield return new IssueTemplateTooShort(this).Create(spinner); + else if (spinner.Duration < warningThreshold) + yield return new IssueTemplateVeryShort(this).Create(spinner); + } + } + + public class IssueTemplateTooShort : IssueTemplate + { + public IssueTemplateTooShort(ICheck check) + : base(check, IssueType.Problem, "This spinner is too short. Auto cannot achieve 1000 points on this.") + { + } + + public Issue Create(Spinner spinner) => new Issue(spinner, this); + } + + public class IssueTemplateVeryShort : IssueTemplate + { + public IssueTemplateVeryShort(ICheck check) + : base(check, IssueType.Warning, "This spinner may be too short. Ensure auto can achieve 1000 points on this.") + { + } + + public Issue Create(Spinner spinner) => new Issue(spinner, this); + } + } +} From 3a5912e35eb03736be9cc6ca6883fa4617583e5c Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:53:25 +0200 Subject: [PATCH 1494/1621] Add new checks to verifiers --- osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs | 4 +++- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 896e904f3f..221723e4cd 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -15,10 +15,12 @@ namespace osu.Game.Rulesets.Osu.Edit { // Compose new CheckOffscreenObjects(), + new CheckTooShortSpinners(), // Spread new CheckTimeDistanceEquality(), - new CheckLowDiffOverlaps() + new CheckLowDiffOverlaps(), + new CheckTooShortSliders(), }; public IEnumerable Run(BeatmapVerifierContext context) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 706eec226c..81f4808789 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -27,7 +27,8 @@ namespace osu.Game.Rulesets.Edit // Compose new CheckUnsnappedObjects(), - new CheckConcurrentObjects() + new CheckConcurrentObjects(), + new CheckZeroLengthObjects(), }; public IEnumerable Run(BeatmapVerifierContext context) From e791669c40559122375e050642839aa363352af8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 17:59:35 +0900 Subject: [PATCH 1495/1621] Fix multiplayer screen buttons showing no text when local user not available --- .../Match/MultiplayerReadyButton.cs | 20 +++++++------------ .../Match/MultiplayerSpectateButton.cs | 14 ++++--------- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index f2dd9a6f25..baf9570209 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -72,25 +71,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { var localUser = Client.LocalUser; - if (localUser == null) - return; + int newCountReady = Room?.Users.Count(u => u.State == MultiplayerUserState.Ready) ?? 0; + int newCountTotal = Room?.Users.Count(u => u.State != MultiplayerUserState.Spectating) ?? 0; - Debug.Assert(Room != null); - - int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready); - int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating); - - string countText = $"({newCountReady} / {newCountTotal} ready)"; - - switch (localUser.State) + switch (localUser?.State) { - case MultiplayerUserState.Idle: + default: button.Text = "Ready"; updateButtonColour(true); break; case MultiplayerUserState.Spectating: case MultiplayerUserState.Ready: + string countText = $"({newCountReady} / {newCountTotal} ready)"; + if (Room?.Host?.Equals(localUser) == true) { button.Text = $"Start match {countText}"; @@ -108,7 +102,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match bool enableButton = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value; // When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready. - if (localUser.State == MultiplayerUserState.Spectating) + if (localUser?.State == MultiplayerUserState.Spectating) enableButton &= Room?.Host?.Equals(localUser) == true && newCountReady > 0; button.Enabled.Value = enableButton; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index 04150902bc..db99c6a5d5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -57,14 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateState() { - var localUser = Client.LocalUser; - - if (localUser == null) - return; - - Debug.Assert(Room != null); - - switch (localUser.State) + switch (Client.LocalUser?.State) { default: button.Text = "Spectate"; @@ -81,7 +73,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match break; } - button.Enabled.Value = Client.Room?.State != MultiplayerRoomState.Closed && !operationInProgress.Value; + button.Enabled.Value = Client.Room != null + && Client.Room.State != MultiplayerRoomState.Closed + && !operationInProgress.Value; } private class ButtonWithTrianglesExposed : TriangleButton From 6b663037e47b0b448d98449134248699dfce9d6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 19:37:02 +0900 Subject: [PATCH 1496/1621] Use `switch` for pattern matching --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index f919ecf839..7794ef8072 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -70,13 +70,15 @@ namespace osu.Game.Rulesets.Osu.Mods // Move hit objects back into the playfield if they are outside of it Vector2 shift = Vector2.Zero; - if (hitObject is HitCircle circle) + switch (hitObject) { - shift = clampHitCircleToPlayfield(circle, current); - } - else if (hitObject is Slider slider) - { - shift = clampSliderToPlayfield(slider, current); + case HitCircle circle: + shift = clampHitCircleToPlayfield(circle, current); + break; + + case Slider slider: + shift = clampSliderToPlayfield(slider, current); + break; } if (shift != Vector2.Zero) From 4314946e10726756eb7a2df717fbbab131a14e19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 19:37:17 +0900 Subject: [PATCH 1497/1621] Reorganise functions to order more logically (hitcircle before slider methods) --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 7794ef8072..1a2e5d92b4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -142,6 +142,23 @@ namespace osu.Game.Rulesets.Osu.Mods current.PositionRandomised = previous.EndPositionRandomised + posRelativeToPrev; } + /// + /// Move the randomised position of a hit circle so that it fits inside the playfield. + /// + /// The deviation from the original randomised position in order to fit within the playfield. + private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo) + { + var previousPosition = objectInfo.PositionRandomised; + objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding( + objectInfo.PositionRandomised, + (float)circle.Radius + ); + + circle.Position = objectInfo.PositionRandomised; + + return objectInfo.PositionRandomised - previousPosition; + } + /// /// Moves the and all necessary nested s into the if they aren't already. /// @@ -254,23 +271,6 @@ namespace osu.Game.Rulesets.Osu.Mods } } - /// - /// Move the randomised position of a hit circle so that it fits inside the playfield. - /// - /// The deviation from the original randomised position in order to fit within the playfield. - private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo) - { - var previousPosition = objectInfo.PositionRandomised; - objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding( - objectInfo.PositionRandomised, - (float)circle.Radius - ); - - circle.Position = objectInfo.PositionRandomised; - - return objectInfo.PositionRandomised - previousPosition; - } - /// /// Clamp a position to playfield, keeping a specified distance from the edges. /// From e7b78b1ea5299dae24c07a16243f24ba4888d170 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 20:26:05 +0900 Subject: [PATCH 1498/1621] Adjust transform logic to hopefully be a bit easier to parse --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 25 ++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 27c352070a..8f9ff81808 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -31,6 +31,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods { @@ -143,22 +144,24 @@ namespace osu.Game.Rulesets.Osu.Mods { if (!(drawable is DrawableHitCircle circle)) return; - var h = (OsuHitObject)drawable.HitObject; + double startTime = circle.HitObject.StartTime; + double preempt = circle.HitObject.TimePreempt; - using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) + using (drawable.BeginAbsoluteSequence(startTime - preempt)) { + // initial state drawable.ScaleTo(0.5f) - .Then().ScaleTo(1f, h.TimePreempt); + .FadeColour(OsuColour.Gray(0.5f)); - var colour = drawable.Colour; - - drawable.FadeColour(OsuColour.Gray(0.45f)) - .Then().Delay(h.TimePreempt - controlPointInfo.TimingPointAt(h.StartTime).BeatLength - undim_duration) - .FadeColour(colour, undim_duration); - - // Remove approach circles - circle.ApproachCircle.Hide(); + // scale to final size + drawable.ScaleTo(1f, preempt); } + + using (drawable.BeginAbsoluteSequence(startTime - controlPointInfo.TimingPointAt(startTime).BeatLength - undim_duration)) + drawable.FadeColour(Color4.White, undim_duration); + + // Remove approach circles + circle.ApproachCircle.Hide(); } #endregion From 9bec53bfa8922bc7de66278a1f0c30e1ba65eb00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Jun 2021 19:31:24 +0200 Subject: [PATCH 1499/1621] Implement osu!-side popover --- .../UserInterface/TestSceneOsuPopover.cs | 103 ++++++++++++++++++ .../Graphics/UserInterfaceV2/OsuPopover.cs | 44 ++++++++ 2 files changed, 147 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneOsuPopover.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuPopover.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuPopover.cs new file mode 100644 index 0000000000..1848cf6a5e --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuPopover.cs @@ -0,0 +1,103 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneOsuPopover : OsuGridTestScene + { + public TestSceneOsuPopover() + : base(1, 2) + { + Cell(0, 0).Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"No OverlayColourProvider", + Font = OsuFont.Default.With(size: 40) + }, + new TriangleButtonWithPopover() + } + }; + + Cell(0, 1).Child = new ColourProvidingContainer(OverlayColourScheme.Orange) + { + RelativeSizeAxes = Axes.Both, + Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"With OverlayColourProvider (orange)", + Font = OsuFont.Default.With(size: 40) + }, + new TriangleButtonWithPopover() + } + } + }; + } + + private class TriangleButtonWithPopover : TriangleButton, IHasPopover + { + public TriangleButtonWithPopover() + { + Width = 100; + Height = 30; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Text = @"open"; + Action = this.ShowPopover; + } + + public Popover GetPopover() => new OsuPopover + { + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"sample text" + }, + new OsuTextBox + { + Width = 150, + Height = 30 + } + } + } + }; + } + + private class ColourProvidingContainer : Container + { + [Cached] + private OverlayColourProvider provider { get; } + + public ColourProvidingContainer(OverlayColourScheme colourScheme) + { + provider = new OverlayColourProvider(colourScheme); + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs new file mode 100644 index 0000000000..33165ea8b5 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.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 JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class OsuPopover : Popover + { + private const float fade_duration = 250; + + public OsuPopover(bool withPadding = true) + { + Content.Padding = withPadding ? new MarginPadding(20) : new MarginPadding(); + Body.Masking = true; + Body.CornerRadius = 10; + Body.Margin = new MarginPadding(10); + Body.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0, 2), + Radius = 5, + Colour = Colour4.Black.Opacity(0.3f) + }; + } + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour osuColour) + { + Background.Colour = Arrow.Colour = colourProvider?.Background4 ?? osuColour.GreySeafoamDarker; + } + + protected override Drawable CreateArrow() => Empty(); + + protected override void PopIn() => this.FadeIn(fade_duration, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(fade_duration, Easing.OutQuint); + } +} From b4961cd12e096e4068beb857844bc5bded30eb11 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Jul 2021 03:18:13 +0300 Subject: [PATCH 1500/1621] Revert "Remove duplicated colour definitions" This reverts commit 9869986c59482f4c64b718e1bceb17d0ebc5d9aa. --- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 3 +-- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 3 +-- osu.Game/Graphics/OsuColour.cs | 4 ++++ osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs | 5 +++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index b871f8bcc7..8a6cfaf688 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -3,7 +3,6 @@ using osu.Framework.Bindables; using osu.Game.Graphics; -using osu.Game.Overlays; using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints @@ -26,7 +25,7 @@ namespace osu.Game.Beatmaps.ControlPoints MaxValue = 10 }; - public override Color4 GetRepresentingColour(OsuColour colours) => OverlayColourProvider.Lime.Colour1; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1; /// /// The speed multiplier at this control point. diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 22808671b0..ec20328fab 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -4,7 +4,6 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; -using osu.Game.Overlays; using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints @@ -21,7 +20,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// private const double default_beat_length = 60000.0 / 60.0; - public override Color4 GetRepresentingColour(OsuColour colours) => OverlayColourProvider.Orange.Colour1; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Orange1; public static readonly TimingControlPoint DEFAULT = new TimingControlPoint { diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 3533ab177c..a44c28eaa6 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -198,6 +198,10 @@ namespace osu.Game.Graphics public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee"); public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff"); + // in latest editor design logic, need to figure out where these sit... + public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66"); + public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966"); + // Content Background public readonly Color4 B5 = Color4Extensions.FromHex(@"222a28"); diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs index 91d6460ea6..421806eea8 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; namespace osu.Game.Overlays.Wiki.Markdown { @@ -65,7 +66,7 @@ namespace osu.Game.Overlays.Wiki.Markdown public string Text { get; set; } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, OsuColour colour) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -80,7 +81,7 @@ namespace osu.Game.Overlays.Wiki.Markdown }, textFlow = parentFlowComponent.CreateTextFlow().With(t => { - t.Colour = OverlayColourProvider.Orange.Colour1; + t.Colour = colour.Orange1; t.Padding = new MarginPadding { Vertical = 10, From 80636be7678cbc001dc0b02e59aac84fbd80c573 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Jul 2021 03:22:00 +0300 Subject: [PATCH 1501/1621] Link `Lime1` and `Orange1` to their `OverlayColourProvider`'s alternative --- osu.Game/Graphics/OsuColour.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index a44c28eaa6..c0bc8fdb76 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -3,6 +3,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Game.Beatmaps; +using osu.Game.Overlays; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osuTK.Graphics; @@ -198,8 +199,14 @@ namespace osu.Game.Graphics public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee"); public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff"); - // in latest editor design logic, need to figure out where these sit... + /// + /// Equivalent to 's . + /// public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66"); + + /// + /// Equivalent to 's . + /// public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966"); // Content Background From ed296462911591152d0ec2f2fe558ab6edc62adb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Jul 2021 12:32:14 +0900 Subject: [PATCH 1502/1621] Remove `IApplicableToDifficulty.ReadFromDifficulty` This was added specifically for `ModDifficultyAdjust`, but turned out to be more of a headache than we expected. We have since removed usage and would hope that this is not required by any other mods. Opting for complete removal rather than obsoletion, as we discovered this was already broken in multiple cases, with fixes being quite logically complex. If you happen to be a ruleset developer relying on this, open an issue and we'll talk you through a better approach (or check what `ModDifficultyAdjust` is doing now for an example). --- osu.Game/OsuGame.cs | 18 ------------------ .../Rulesets/Mods/IApplicableToDifficulty.cs | 7 ------- 2 files changed, 25 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c25b520892..8119df43ac 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -525,16 +525,11 @@ namespace osu.Game private void beatmapChanged(ValueChangedEvent beatmap) { beatmap.OldValue?.CancelAsyncLoad(); - - updateModDefaults(); - beatmap.NewValue?.BeginAsyncLoad(); } private void modsChanged(ValueChangedEvent> mods) { - updateModDefaults(); - // a lease may be taken on the mods bindable, at which point we can't really ensure valid mods. if (SelectedMods.Disabled) return; @@ -546,19 +541,6 @@ namespace osu.Game } } - private void updateModDefaults() - { - BeatmapDifficulty baseDifficulty = Beatmap.Value.BeatmapInfo.BaseDifficulty; - - if (baseDifficulty != null && SelectedMods.Value.Any(m => m is IApplicableToDifficulty)) - { - var adjustedDifficulty = baseDifficulty.Clone(); - - foreach (var mod in SelectedMods.Value.OfType()) - mod.ReadFromDifficulty(adjustedDifficulty); - } - } - #endregion private PerformFromMenuRunner performFromMainMenuTask; diff --git a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs index 34198da722..42b520ab26 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs @@ -10,13 +10,6 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToDifficulty : IApplicableMod { - /// - /// Called when a beatmap is changed. Can be used to read default values. - /// Any changes made will not be preserved. - /// - /// The difficulty to read from. - void ReadFromDifficulty(BeatmapDifficulty difficulty); - /// /// Called post beatmap conversion. Can be used to apply changes to difficulty attributes. /// From 7f432665e52774a5bef21cf930ff2ac1ac0527f6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 14 Jul 2021 14:38:38 +0900 Subject: [PATCH 1503/1621] Preserve Y position of hit objects in osu!catch --- .../Beatmaps/CatchBeatmapConverter.cs | 11 +++++---- .../Objects/CatchHitObject.cs | 24 ++++++++++++++++--- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 5 +--- .../Objects/Legacy/Catch/ConvertHit.cs | 9 +++++-- .../Legacy/Catch/ConvertHitObjectParser.cs | 4 ++-- .../Objects/Legacy/Catch/ConvertSlider.cs | 9 +++++-- 6 files changed, 45 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 34964fc4ae..7774a7da09 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -23,7 +23,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap, CancellationToken cancellationToken) { - var positionData = obj as IHasXPosition; + var xPositionData = obj as IHasXPosition; + var yPositionData = obj as IHasYPosition; var comboData = obj as IHasCombo; switch (obj) @@ -36,10 +37,11 @@ namespace osu.Game.Rulesets.Catch.Beatmaps Path = curveData.Path, NodeSamples = curveData.NodeSamples, RepeatCount = curveData.RepeatCount, - X = positionData?.X ?? 0, + X = xPositionData?.X ?? 0, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, - LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0 + LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0, + LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y }.Yield(); case IHasDuration endTime: @@ -59,7 +61,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps Samples = obj.Samples, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, - X = positionData?.X ?? 0 + X = xPositionData?.X ?? 0, + LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y }.Yield(); } } diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 0b8c0e28a7..f979e3e0ca 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -9,10 +9,11 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Catch.Objects { - public abstract class CatchHitObject : HitObject, IHasXPosition, IHasComboInformation + public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation { public const float OBJECT_RADIUS = 64; @@ -31,8 +32,6 @@ namespace osu.Game.Rulesets.Catch.Objects set => OriginalXBindable.Value = value; } - float IHasXPosition.X => OriginalXBindable.Value; - public readonly Bindable XOffsetBindable = new Bindable(); /// @@ -131,5 +130,24 @@ namespace osu.Game.Rulesets.Catch.Objects } protected override HitWindows CreateHitWindows() => HitWindows.Empty; + + #region Hit object conversion + + // The half of the height of the osu! playfield. + public const float DEFAULT_LEGACY_CONVERT_Y = 192; + + /// + /// The Y position of the hit object is not used in the normal osu!catch gameplay. + /// It is preserved to maximize the backward compatibility with the legacy editor, in which the mappers use the Y position to organize the patterns. + /// + public float LegacyConvertedY { get; set; } = DEFAULT_LEGACY_CONVERT_Y; + + float IHasXPosition.X => OriginalX; + + float IHasYPosition.Y => LegacyConvertedY; + + Vector2 IHasPosition.Position => new Vector2(OriginalX, LegacyConvertedY); + + #endregion } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index acbf57d25f..f14f6ec10c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -251,11 +251,8 @@ namespace osu.Game.Beatmaps.Formats switch (beatmap.BeatmapInfo.RulesetID) { case 0: - position = ((IHasPosition)hitObject).Position; - break; - case 2: - position.X = ((IHasXPosition)hitObject).X; + position = ((IHasPosition)hitObject).Position; break; case 3: diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs index 19722fb796..12b4812824 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs @@ -2,15 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; +using osuTK; namespace osu.Game.Rulesets.Objects.Legacy.Catch { /// /// Legacy osu!catch Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : ConvertHitObject, IHasCombo, IHasXPosition + internal sealed class ConvertHit : ConvertHitObject, IHasPosition, IHasCombo { - public float X { get; set; } + public float X => Position.X; + + public float Y => Position.Y; + + public Vector2 Position { get; set; } public bool NewCombo { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index c10c8dc30f..c29179f749 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch return new ConvertHit { - X = position.X, + Position = position, NewCombo = newCombo, ComboOffset = comboOffset }; @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch return new ConvertSlider { - X = position.X, + Position = position, NewCombo = FirstObject || newCombo, ComboOffset = comboOffset, Path = new SliderPath(controlPoints, length), diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs index 56790629b4..fb1afed3b4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs @@ -2,15 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; +using osuTK; namespace osu.Game.Rulesets.Objects.Legacy.Catch { /// /// Legacy osu!catch Slider-type, used for parsing Beatmaps. /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo { - public float X { get; set; } + public float X => Position.X; + + public float Y => Position.Y; + + public Vector2 Position { get; set; } public bool NewCombo { get; set; } From fdfd82aec42256ca1f44ab3b576c5c410640316d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Jul 2021 23:23:44 +0900 Subject: [PATCH 1504/1621] Add elastic scale on appear --- osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index 33165ea8b5..fff18e615f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -14,10 +14,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 public class OsuPopover : Popover { private const float fade_duration = 250; + private const double scale_duration = 500; public OsuPopover(bool withPadding = true) { Content.Padding = withPadding ? new MarginPadding(20) : new MarginPadding(); + Body.Masking = true; Body.CornerRadius = 10; Body.Margin = new MarginPadding(10); @@ -38,7 +40,16 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override Drawable CreateArrow() => Empty(); - protected override void PopIn() => this.FadeIn(fade_duration, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(fade_duration, Easing.OutQuint); + protected override void PopIn() + { + this.ScaleTo(1, scale_duration, Easing.OutElasticHalf); + this.FadeIn(fade_duration, Easing.OutQuint); + } + + protected override void PopOut() + { + this.ScaleTo(0.7f, scale_duration, Easing.OutQuint); + this.FadeOut(fade_duration, Easing.OutQuint); + } } } From c6116676ebb9e090a82717537e09027399009a9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Jul 2021 23:23:48 +0900 Subject: [PATCH 1505/1621] 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 cd57d7478e..171a0862a1 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 eb7a0141c7..b2f1d6507f 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 2e5fab758d..dc15df6ea6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 339fab75a83a56df8124e5794170d9caaf7976bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Jul 2021 23:27:03 +0900 Subject: [PATCH 1506/1621] Rename colour variable in line with other usages --- osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index fff18e615f..c07a5de1e4 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -33,9 +33,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 } [BackgroundDependencyLoader(true)] - private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour osuColour) + private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour colours) { - Background.Colour = Arrow.Colour = colourProvider?.Background4 ?? osuColour.GreySeafoamDarker; + Background.Colour = Arrow.Colour = colourProvider?.Background4 ?? colours.GreySeafoamDarker; } protected override Drawable CreateArrow() => Empty(); From cc09a8b5badf002dedaa80bd0b57cb94e9abff75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Jul 2021 23:55:46 +0900 Subject: [PATCH 1507/1621] Update to use `OsuPopover` implementation --- osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index b8c4b57378..7f4ea0908d 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -23,6 +23,7 @@ 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.Input.Bindings; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; @@ -345,7 +346,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } - public class PasswordEntryPopover : BasicPopover + public class PasswordEntryPopover : OsuPopover { private readonly Room room; From 9d693c75cfad98bd011bf0f75427f21372994c38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Jul 2021 23:56:52 +0900 Subject: [PATCH 1508/1621] Add `Schedule` to restore password text box focus behaviour --- osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 7f4ea0908d..84c20a6acc 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -389,7 +389,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - GetContainingInputManager().ChangeFocus(passwordTextbox); + Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox)); } } } From 1041a5fb940ad95775853c5ed56fe41309ba1c8e Mon Sep 17 00:00:00 2001 From: Evan Boehs <51836263+boehs@users.noreply.github.com> Date: Wed, 14 Jul 2021 14:39:45 -0400 Subject: [PATCH 1509/1621] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e95c12cfdc..016bd7d922 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ We are accepting bug reports (please report with as much detail as possible and - Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer). - You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management). -- Read peppy's [latest blog post](https://blog.ppy.sh/a-definitive-lazer-faq/) exploring where the project is currently and the roadmap going forward. +- Read peppy's [blog post](https://blog.ppy.sh/a-definitive-lazer-faq/) exploring where the project is currently and the roadmap going forward. ## Running osu! From 687c9aa33d44de87989fd5f9f1a9359c5bcc2880 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 12:37:52 +0900 Subject: [PATCH 1510/1621] Add tooltip and keywords for "high precision" setting --- osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index e87572e2ca..eefe160b94 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -47,7 +47,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input new SettingsCheckbox { LabelText = "High precision mouse", - Current = relativeMode + TooltipText = "Attempts to bypass any operation system mouse acceleration. On windows, this is equivalent to what used to be known as \"Raw Input\".", + Current = relativeMode, + Keywords = new[] { "raw", "input", "relative", "cursor" } }, new SensitivitySetting { From 5b91111edabdfa7e1b7b2123c5322d73ffdd48ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 12:50:34 +0900 Subject: [PATCH 1511/1621] Update `SettingsSubsection.Header` to accept a `LocalisableString` --- .../ManiaSettingsSubsection.cs | 2 +- .../UI/OsuSettingsSubsection.cs | 3 ++- osu.Game/Localisation/MouseSettingsStrings.cs | 19 +++++++++++++++++++ .../KeyBinding/GlobalKeyBindingsSection.cs | 11 ++++++----- .../KeyBinding/VariantBindingsSubsection.cs | 3 ++- .../Sections/Audio/AudioDevicesSettings.cs | 2 +- .../Settings/Sections/Audio/OffsetSettings.cs | 2 +- .../Settings/Sections/Audio/VolumeSettings.cs | 3 ++- .../Sections/Debug/GeneralSettings.cs | 3 ++- .../Settings/Sections/Debug/MemorySettings.cs | 3 ++- .../Sections/Gameplay/GeneralSettings.cs | 3 ++- .../Sections/Gameplay/ModsSettings.cs | 3 ++- .../Sections/General/LanguageSettings.cs | 3 ++- .../Sections/General/UpdateSettings.cs | 3 ++- .../Sections/Graphics/DetailSettings.cs | 3 ++- .../Sections/Graphics/LayoutSettings.cs | 2 +- .../Sections/Graphics/RendererSettings.cs | 3 ++- .../Sections/Input/BindingSettings.cs | 3 ++- .../Settings/Sections/Input/MouseSettings.cs | 3 ++- .../Settings/Sections/Input/TabletSettings.cs | 3 ++- .../Settings/Sections/InputSection.cs | 3 ++- .../Sections/Maintenance/GeneralSettings.cs | 3 ++- .../Online/AlertsAndPrivacySettings.cs | 3 ++- .../Sections/Online/IntegrationSettings.cs | 3 ++- .../Settings/Sections/Online/WebSettings.cs | 3 ++- .../Sections/UserInterface/GeneralSettings.cs | 2 +- .../UserInterface/MainMenuSettings.cs | 3 ++- .../UserInterface/SongSelectSettings.cs | 2 +- .../Overlays/Settings/SettingsSubsection.cs | 7 ++++--- 29 files changed, 75 insertions(+), 34 deletions(-) create mode 100644 osu.Game/Localisation/MouseSettingsStrings.cs diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index f89750a96e..36fa336d0c 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania { public class ManiaSettingsSubsection : RulesetSettingsSubsection { - protected override string Header => "osu!mania"; + protected override LocalisableString Header => "osu!mania"; public ManiaSettingsSubsection(ManiaRuleset ruleset) : base(ruleset) diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index 705ba3e929..a4c0381d16 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.UI; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.UI { public class OsuSettingsSubsection : RulesetSettingsSubsection { - protected override string Header => "osu!"; + protected override LocalisableString Header => "osu!"; public OsuSettingsSubsection(Ruleset ruleset) : base(ruleset) diff --git a/osu.Game/Localisation/MouseSettingsStrings.cs b/osu.Game/Localisation/MouseSettingsStrings.cs new file mode 100644 index 0000000000..0260def409 --- /dev/null +++ b/osu.Game/Localisation/MouseSettingsStrings.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.Localisation; + +namespace osu.Game.Localisation +{ + public static class MouseSettingsStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.MouseSettings"; + + /// + /// "Mouse" + /// + public static LocalisableString Mouse => new TranslatableString(getKey(@"mouse"), @"Mouse"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index c905397e77..6ea4209cce 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Input.Bindings; using osu.Game.Overlays.Settings; @@ -28,7 +29,7 @@ namespace osu.Game.Overlays.KeyBinding private class DefaultBindingsSubsection : KeyBindingsSubsection { - protected override string Header => string.Empty; + protected override LocalisableString Header => string.Empty; public DefaultBindingsSubsection(GlobalActionContainer manager) : base(null) @@ -39,7 +40,7 @@ namespace osu.Game.Overlays.KeyBinding private class SongSelectKeyBindingSubsection : KeyBindingsSubsection { - protected override string Header => "Song Select"; + protected override LocalisableString Header => "Song Select"; public SongSelectKeyBindingSubsection(GlobalActionContainer manager) : base(null) @@ -50,7 +51,7 @@ namespace osu.Game.Overlays.KeyBinding private class InGameKeyBindingsSubsection : KeyBindingsSubsection { - protected override string Header => "In Game"; + protected override LocalisableString Header => "In Game"; public InGameKeyBindingsSubsection(GlobalActionContainer manager) : base(null) @@ -61,7 +62,7 @@ namespace osu.Game.Overlays.KeyBinding private class AudioControlKeyBindingsSubsection : KeyBindingsSubsection { - protected override string Header => "Audio"; + protected override LocalisableString Header => "Audio"; public AudioControlKeyBindingsSubsection(GlobalActionContainer manager) : base(null) @@ -72,7 +73,7 @@ namespace osu.Game.Overlays.KeyBinding private class EditorKeyBindingsSubsection : KeyBindingsSubsection { - protected override string Header => "Editor"; + protected override LocalisableString Header => "Editor"; public EditorKeyBindingsSubsection(GlobalActionContainer manager) : base(null) diff --git a/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs index 861d59c8f4..7618a42282 100644 --- a/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs @@ -1,13 +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 osu.Framework.Localisation; using osu.Game.Rulesets; namespace osu.Game.Overlays.KeyBinding { public class VariantBindingsSubsection : KeyBindingsSubsection { - protected override string Header { get; } + protected override LocalisableString Header { get; } public VariantBindingsSubsection(RulesetInfo ruleset, int variant) : base(variant) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs index b31e7dc45b..d64f176468 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { public class AudioDevicesSettings : SettingsSubsection { - protected override string Header => "Devices"; + protected override LocalisableString Header => "Devices"; [Resolved] private AudioManager audio { get; set; } diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index 1ae297f2a9..7f2e377c83 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -11,7 +11,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { public class OffsetSettings : SettingsSubsection { - protected override string Header => "Offset Adjustment"; + protected override LocalisableString Header => "Offset Adjustment"; [BackgroundDependencyLoader] private void load(OsuConfigManager config) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs index c172a76ab9..8f88b03471 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs @@ -4,13 +4,14 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; namespace osu.Game.Overlays.Settings.Sections.Audio { public class VolumeSettings : SettingsSubsection { - protected override string Header => "Volume"; + protected override LocalisableString Header => "Volume"; [BackgroundDependencyLoader] private void load(AudioManager audio, OsuConfigManager config) diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs index 4a9c9bd8a2..2b868cab85 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Game.Screens.Import; @@ -11,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Debug { public class GeneralSettings : SettingsSubsection { - protected override string Header => "General"; + protected override LocalisableString Header => "General"; [BackgroundDependencyLoader(true)] private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig, OsuGame game) diff --git a/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs index db64c9a8ac..bf7fb351c0 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs @@ -4,13 +4,14 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Framework.Platform; namespace osu.Game.Overlays.Settings.Sections.Debug { public class MemorySettings : SettingsSubsection { - protected override string Header => "Memory"; + protected override LocalisableString Header => "Memory"; [BackgroundDependencyLoader] private void load(FrameworkDebugConfigManager config, GameHost host) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 0b5ec4f338..353292606f 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -4,6 +4,7 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; @@ -11,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { public class GeneralSettings : SettingsSubsection { - protected override string Header => "General"; + protected override LocalisableString Header => "General"; [BackgroundDependencyLoader] private void load(OsuConfigManager config) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs index 2b2fb9cef7..ec9ddde2da 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs @@ -4,13 +4,14 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Localisation; using osu.Game.Configuration; namespace osu.Game.Overlays.Settings.Sections.Gameplay { public class ModsSettings : SettingsSubsection { - protected override string Header => "Mods"; + protected override LocalisableString Header => "Mods"; public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "mod" }); diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs index dfcdb8e340..c6c752e2fd 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Extensions; using osu.Game.Localisation; @@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.General private SettingsDropdown languageSelection; private Bindable frameworkLocale; - protected override string Header => "Language"; + protected override LocalisableString Header => "Language"; [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index c213313559..dd20e1d7ef 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Configuration; @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.General [Resolved(CanBeNull = true)] private UpdateManager updateManager { get; set; } - protected override string Header => "Updates"; + protected override LocalisableString Header => "Updates"; private SettingsButton checkForUpdatesButton; diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index 30caa45995..f889cfca0f 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -3,13 +3,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; namespace osu.Game.Overlays.Settings.Sections.Graphics { public class DetailSettings : SettingsSubsection { - protected override string Header => "Detail Settings"; + protected override LocalisableString Header => "Detail Settings"; [BackgroundDependencyLoader] private void load(OsuConfigManager config) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 669753d2cb..91208cb78a 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { public class LayoutSettings : SettingsSubsection { - protected override string Header => "Layout"; + protected override LocalisableString Header => "Layout"; private FillFlowContainer> scalingSettings; diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 70225ff6b8..2210c7911e 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Configuration; @@ -11,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { public class RendererSettings : SettingsSubsection { - protected override string Header => "Renderer"; + protected override LocalisableString Header => "Renderer"; private SettingsEnumDropdown frameLimiterDropdown; diff --git a/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs index 79c73863cf..e68cd606e5 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs @@ -2,12 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Localisation; namespace osu.Game.Overlays.Settings.Sections.Input { public class BindingSettings : SettingsSubsection { - protected override string Header => "Shortcut and gameplay bindings"; + protected override LocalisableString Header => "Shortcut and gameplay bindings"; public BindingSettings(KeyBindingPanel keyConfig) { diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index eefe160b94..b39507da62 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -10,6 +10,7 @@ using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Input; +using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Input { @@ -17,7 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { private readonly MouseHandler mouseHandler; - protected override string Header => "Mouse"; + protected override LocalisableString Header => MouseSettingsStrings.Mouse; private Bindable handlerSensitivity; diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index d770c18878..e9ed3378a2 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Handlers.Tablet; +using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics.Sprites; @@ -52,7 +53,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private OsuSpriteText noTabletMessage; - protected override string Header => "Tablet"; + protected override LocalisableString Header => "Tablet"; public TabletSettings(ITabletHandler tabletHandler) { diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index 6e99891794..366f39388a 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -9,6 +9,7 @@ using osu.Framework.Input.Handlers.Joystick; using osu.Framework.Input.Handlers.Midi; using osu.Framework.Input.Handlers.Mouse; using osu.Framework.Input.Handlers.Tablet; +using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Overlays.Settings.Sections.Input; @@ -100,7 +101,7 @@ namespace osu.Game.Overlays.Settings.Sections }; } - protected override string Header => handler.Description; + protected override LocalisableString Header => handler.Description; } } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index a38ca81e23..b9a408b1f8 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Database; @@ -17,7 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class GeneralSettings : SettingsSubsection { - protected override string Header => "General"; + protected override LocalisableString Header => "General"; private TriangleButton importBeatmapsButton; private TriangleButton importScoresButton; diff --git a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs index b0f6400d4f..3a2de2ee36 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs @@ -3,13 +3,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; namespace osu.Game.Overlays.Settings.Sections.Online { public class AlertsAndPrivacySettings : SettingsSubsection { - protected override string Header => "Alerts and Privacy"; + protected override LocalisableString Header => "Alerts and Privacy"; [BackgroundDependencyLoader] private void load(OsuConfigManager config) diff --git a/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs index d2867962c0..f2012f0d9c 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs @@ -3,13 +3,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; namespace osu.Game.Overlays.Settings.Sections.Online { public class IntegrationSettings : SettingsSubsection { - protected override string Header => "Integrations"; + protected override LocalisableString Header => "Integrations"; [BackgroundDependencyLoader] private void load(OsuConfigManager config) diff --git a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs index 59bcbe4d89..89e7b096f3 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs @@ -3,13 +3,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; namespace osu.Game.Overlays.Settings.Sections.Online { public class WebSettings : SettingsSubsection { - protected override string Header => "Web"; + protected override LocalisableString Header => "Web"; [BackgroundDependencyLoader] private void load(OsuConfigManager config) diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index a6eb008623..4b26645ef3 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -11,7 +11,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface { public class GeneralSettings : SettingsSubsection { - protected override string Header => "General"; + protected override LocalisableString Header => "General"; [BackgroundDependencyLoader] private void load(OsuConfigManager config) diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs index 5f703ed5a4..81bbcbb54a 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Online.API; using osu.Game.Users; @@ -12,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface { public class MainMenuSettings : SettingsSubsection { - protected override string Header => "Main Menu"; + protected override LocalisableString Header => "Main Menu"; private IBindable user; diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 2470c0a6c5..587155eb0d 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private Bindable minStars; private Bindable maxStars; - protected override string Header => "Song Select"; + protected override LocalisableString Header => "Song Select"; [BackgroundDependencyLoader] private void load(OsuConfigManager config) diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index 6abf6283b9..df32424b67 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -8,6 +8,7 @@ using osu.Game.Graphics.Sprites; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Graphics; @@ -20,10 +21,10 @@ namespace osu.Game.Overlays.Settings protected readonly FillFlowContainer FlowContent; - protected abstract string Header { get; } + protected abstract LocalisableString Header { get; } public IEnumerable FilterableChildren => Children.OfType(); - public virtual IEnumerable FilterTerms => new[] { Header }; + public virtual IEnumerable FilterTerms => new[] { Header.ToString() }; public bool MatchingFilter { @@ -54,7 +55,7 @@ namespace osu.Game.Overlays.Settings { new OsuSpriteText { - Text = Header.ToUpperInvariant(), + Text = Header.ToString().ToUpper(), // TODO: Add localisation support after https://github.com/ppy/osu-framework/pull/4603 is merged. Margin = new MarginPadding { Vertical = 30, Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }, Font = OsuFont.GetFont(weight: FontWeight.Bold), }, From d58534dce60161c0207d27c160306dc1267021d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 13:01:21 +0900 Subject: [PATCH 1512/1621] Update `CommonStrings` to latest localiser tools output --- osu.Game/Localisation/CommonStrings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index 50e01f06fc..ad87986554 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -14,6 +14,6 @@ namespace osu.Game.Localisation /// public static LocalisableString Cancel => new TranslatableString(getKey(@"cancel"), @"Cancel"); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } From 3f3adfe2979ac732bc2689b63d9066450a12833f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 13:07:35 +0900 Subject: [PATCH 1513/1621] Add localisation support for `MouseSettings` --- osu.Game/Localisation/MouseSettingsStrings.cs | 42 ++++++++++++++++++- .../Settings/Sections/Input/MouseSettings.cs | 18 ++++---- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/osu.Game/Localisation/MouseSettingsStrings.cs b/osu.Game/Localisation/MouseSettingsStrings.cs index 0260def409..9b1f7fe4c5 100644 --- a/osu.Game/Localisation/MouseSettingsStrings.cs +++ b/osu.Game/Localisation/MouseSettingsStrings.cs @@ -14,6 +14,46 @@ namespace osu.Game.Localisation /// public static LocalisableString Mouse => new TranslatableString(getKey(@"mouse"), @"Mouse"); + /// + /// "Not applicable in full screen mode" + /// + public static LocalisableString NotApplicableFullscreen => new TranslatableString(getKey(@"not_applicable_full_screen"), @"Not applicable in full screen mode"); + + /// + /// "High precision mouse" + /// + public static LocalisableString HighPrecisionMouse => new TranslatableString(getKey(@"high_precision_mouse"), @"High precision mouse"); + + /// + /// "Attempts to bypass any operation system mouse acceleration. On windows, this is equivalent to what used to be known as "Raw Input"." + /// + public static LocalisableString HighPrecisionMouseTooltip => new TranslatableString(getKey(@"high_precision_mouse_tooltip"), @"Attempts to bypass any operation system mouse acceleration. On windows, this is equivalent to what used to be known as ""Raw Input""."); + + /// + /// "Confine mouse cursor to window" + /// + public static LocalisableString ConfineMouseMode => new TranslatableString(getKey(@"confine_mouse_mode"), @"Confine mouse cursor to window"); + + /// + /// "Disable mouse wheel during gameplay" + /// + public static LocalisableString DisableMouseWheel => new TranslatableString(getKey(@"disable_mouse_wheel"), @"Disable mouse wheel during gameplay"); + + /// + /// "Disable mouse buttons during gameplay" + /// + public static LocalisableString DisableMouseButtons => new TranslatableString(getKey(@"disable_mouse_buttons"), @"Disable mouse buttons during gameplay"); + + /// + /// "Enable high precision mouse to adjust sensitivity" + /// + public static LocalisableString EnableHighPrecisionForSensitivityAdjust => new TranslatableString(getKey(@"enable_high_precision_for_sensitivity_adjust"), @"Enable high precision mouse to adjust sensitivity"); + + /// + /// "Cursor sensitivity" + /// + public static LocalisableString CursorSensitivity => new TranslatableString(getKey(@"cursor_sensitivity"), @"Cursor sensitivity"); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index b39507da62..753096a207 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -47,29 +47,29 @@ namespace osu.Game.Overlays.Settings.Sections.Input { new SettingsCheckbox { - LabelText = "High precision mouse", - TooltipText = "Attempts to bypass any operation system mouse acceleration. On windows, this is equivalent to what used to be known as \"Raw Input\".", + LabelText = MouseSettingsStrings.HighPrecisionMouse, + TooltipText = MouseSettingsStrings.HighPrecisionMouseTooltip, Current = relativeMode, - Keywords = new[] { "raw", "input", "relative", "cursor" } + Keywords = new[] { @"raw", @"input", @"relative", @"cursor" } }, new SensitivitySetting { - LabelText = "Cursor sensitivity", + LabelText = MouseSettingsStrings.CursorSensitivity, Current = localSensitivity }, confineMouseModeSetting = new SettingsEnumDropdown { - LabelText = "Confine mouse cursor to window", + LabelText = MouseSettingsStrings.ConfineMouseMode, Current = osuConfig.GetBindable(OsuSetting.ConfineMouseMode) }, new SettingsCheckbox { - LabelText = "Disable mouse wheel during gameplay", + LabelText = MouseSettingsStrings.DisableMouseWheel, Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel) }, new SettingsCheckbox { - LabelText = "Disable mouse buttons during gameplay", + LabelText = MouseSettingsStrings.DisableMouseButtons, Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons) }, }; @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (isFullscreen) { confineMouseModeSetting.Current.Disabled = true; - confineMouseModeSetting.TooltipText = "Not applicable in full screen mode"; + confineMouseModeSetting.TooltipText = MouseSettingsStrings.NotApplicableFullscreen; } else { @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private class SensitivitySlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Disabled ? "enable high precision mouse to adjust sensitivity" : $"{base.TooltipText}x"; + public override LocalisableString TooltipText => Current.Disabled ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust : $"{base.TooltipText}x"; } } } From 8cc2d2e79e6679c3c541b6999bc1b8dbdd332f1d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 13:51:28 +0900 Subject: [PATCH 1514/1621] Update beat synced container tests to be usable --- .../TestSceneBeatSyncedContainer.cs | 82 ++++++++++++------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 82b7e65c4f..c952163ba5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -5,18 +5,18 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; +using osu.Game.Rulesets.Osu; using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface @@ -24,37 +24,55 @@ namespace osu.Game.Tests.Visual.UserInterface [TestFixture] public class TestSceneBeatSyncedContainer : OsuTestScene { - private readonly NowPlayingOverlay np; + private BeatContainer beatContainer; + private DecoupleableInterpolatingFramedClock decoupledClock; - public TestSceneBeatSyncedContainer() + [SetUpSteps] + public void SetUpSteps() { - Clock = new FramedClock(); - Clock.ProcessFrame(); - - AddRange(new Drawable[] + AddStep("Set beatmap", () => { - new BeatContainer - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - }, - np = new NowPlayingOverlay - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - } + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); }); + + AddStep("Create beat sync container", () => + { + Children = new Drawable[] + { + beatContainer = new BeatContainer + { + Clock = decoupledClock = new DecoupleableInterpolatingFramedClock + { + IsCoupled = false, + }, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, + }; + + decoupledClock.ChangeSource(Beatmap.Value.Track); + }); + + AddStep("Start playback", () => decoupledClock.Start()); } - protected override void LoadComplete() + [Test] + public void TestFirstBeatAtFirstTimingPoint() { - base.LoadComplete(); - np.ToggleVisibility(); + AddStep("Set time before zero", () => + { + decoupledClock.Seek(-1000); + }); + + AddStep("bind event", () => + { + beatContainer.NewBeat = (i, point, effectControlPoint, channelAmplitudes) => { }; + }); } private class BeatContainer : BeatSyncedContainer { - private const int flash_layer_heigth = 150; + private const int flash_layer_height = 150; private readonly InfoString timingPointCount; private readonly InfoString currentTimingPoint; @@ -64,12 +82,10 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly InfoString adjustedBeatLength; private readonly InfoString timeUntilNextBeat; private readonly InfoString timeSinceLastBeat; + private readonly InfoString currentTime; private readonly Box flashLayer; - [Resolved] - private MusicController musicController { get; set; } - public BeatContainer() { RelativeSizeAxes = Axes.X; @@ -82,7 +98,7 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Bottom = flash_layer_heigth }, + Margin = new MarginPadding { Bottom = flash_layer_height }, Children = new Drawable[] { new Box @@ -98,6 +114,7 @@ namespace osu.Game.Tests.Visual.UserInterface Direction = FillDirection.Vertical, Children = new Drawable[] { + currentTime = new InfoString(@"Current time"), timingPointCount = new InfoString(@"Timing points amount"), currentTimingPoint = new InfoString(@"Current timing point"), beatCount = new InfoString(@"Beats amount (in the current timing point)"), @@ -116,7 +133,7 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, - Height = flash_layer_heigth, + Height = flash_layer_height, Children = new Drawable[] { new Box @@ -164,7 +181,7 @@ namespace osu.Game.Tests.Visual.UserInterface if (timingPoints.Count == 0) return 0; if (timingPoints[^1] == current) - return (int)Math.Ceiling((musicController.CurrentTrack.Length - current.Time) / current.BeatLength); + return (int)Math.Ceiling((Clock.CurrentTime - current.Time) / current.BeatLength); return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); } @@ -174,8 +191,11 @@ namespace osu.Game.Tests.Visual.UserInterface base.Update(); timeUntilNextBeat.Value = TimeUntilNextBeat; timeSinceLastBeat.Value = TimeSinceLastBeat; + currentTime.Value = Clock.CurrentTime; } + public Action NewBeat; + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); @@ -187,7 +207,9 @@ namespace osu.Game.Tests.Visual.UserInterface beatsPerMinute.Value = 60000 / timingPoint.BeatLength; adjustedBeatLength.Value = timingPoint.BeatLength; - flashLayer.FadeOutFromOne(timingPoint.BeatLength); + flashLayer.FadeOutFromOne(timingPoint.BeatLength / 4); + + NewBeat?.Invoke(beatIndex, timingPoint, effectPoint, amplitudes); } } @@ -200,7 +222,7 @@ namespace osu.Game.Tests.Visual.UserInterface public double Value { - set => valueText.Text = $"{value:G}"; + set => valueText.Text = $"{value:0.##}"; } public InfoString(string header) From a3129ad00e22dc44c9f4965f8ef0702b22ce6a91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 14:30:38 +0900 Subject: [PATCH 1515/1621] Refactor `BeatSyncedContainer` to support `GameplayClock` --- .../TestSceneBeatSyncedContainer.cs | 35 ++++++----- .../Containers/BeatSyncedContainer.cs | 62 ++++++++++++++----- 2 files changed, 66 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index c952163ba5..f58f9fcaaf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -11,12 +11,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play; using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface @@ -25,7 +25,8 @@ namespace osu.Game.Tests.Visual.UserInterface public class TestSceneBeatSyncedContainer : OsuTestScene { private BeatContainer beatContainer; - private DecoupleableInterpolatingFramedClock decoupledClock; + + private MasterGameplayClockContainer gameplayClockContainer; [SetUpSteps] public void SetUpSteps() @@ -39,21 +40,18 @@ namespace osu.Game.Tests.Visual.UserInterface { Children = new Drawable[] { - beatContainer = new BeatContainer + gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0) { - Clock = decoupledClock = new DecoupleableInterpolatingFramedClock + Child = beatContainer = new BeatContainer { - IsCoupled = false, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, }, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - }, + } }; - - decoupledClock.ChangeSource(Beatmap.Value.Track); }); - AddStep("Start playback", () => decoupledClock.Start()); + AddStep("Start playback", () => gameplayClockContainer.Start()); } [Test] @@ -61,7 +59,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("Set time before zero", () => { - decoupledClock.Seek(-1000); + gameplayClockContainer.Seek(-1000); }); AddStep("bind event", () => @@ -150,8 +148,13 @@ namespace osu.Game.Tests.Visual.UserInterface } } }; + } - Beatmap.ValueChanged += delegate + protected override void LoadComplete() + { + base.LoadComplete(); + + Beatmap.BindValueChanged(_ => { timingPointCount.Value = 0; currentTimingPoint.Value = 0; @@ -161,7 +164,7 @@ namespace osu.Game.Tests.Visual.UserInterface adjustedBeatLength.Value = 0; timeUntilNextBeat.Value = 0; timeSinceLastBeat.Value = 0; - }; + }, true); } private List timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ToList(); @@ -181,7 +184,7 @@ namespace osu.Game.Tests.Visual.UserInterface if (timingPoints.Count == 0) return 0; if (timingPoints[^1] == current) - return (int)Math.Ceiling((Clock.CurrentTime - current.Time) / current.BeatLength); + return (int)Math.Ceiling((BeatSyncClock.CurrentTime - current.Time) / current.BeatLength); return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); } @@ -191,7 +194,7 @@ namespace osu.Game.Tests.Visual.UserInterface base.Update(); timeUntilNextBeat.Value = TimeUntilNextBeat; timeSinceLastBeat.Value = TimeSinceLastBeat; - currentTime.Value = Clock.CurrentTime; + currentTime.Value = BeatSyncClock.CurrentTime; } public Action NewBeat; diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index e2a0c09a6b..cb26406d64 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -1,19 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Screens.Play; namespace osu.Game.Graphics.Containers { + /// + /// A container which fires a callback when a new beat is reached. + /// Consumes a parent or (whichever is first available). + /// + /// + /// This container does not set its own clock to the source used for beat matching. + /// This means that if the beat source clock is playing faster or slower, animations may unexpectedly overlap. + /// Make sure this container's Clock is also set to the expected source (or within a parent element which provides this). + /// public class BeatSyncedContainer : Container { - protected readonly IBindable Beatmap = new Bindable(); - private int lastBeat; private TimingControlPoint lastTimingPoint; @@ -45,15 +55,45 @@ namespace osu.Game.Graphics.Containers protected bool IsBeatSyncedWithTrack { get; private set; } + protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + } + + [Resolved] + protected IBindable Beatmap { get; private set; } + + [Resolved(canBeNull: true)] + protected GameplayClock GameplayClock { get; private set; } + + protected IClock BeatSyncClock + { + get + { + if (GameplayClock != null) + return GameplayClock; + + if (Beatmap.Value.TrackLoaded) + return Beatmap.Value.Track; + + return null; + } + } + protected override void Update() { ITrack track = null; IBeatmap beatmap = null; double currentTrackTime = 0; + TimingControlPoint timingPoint = null; EffectControlPoint effectPoint = null; + var clock = BeatSyncClock; + + if (clock == null) + return; + if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded) { track = Beatmap.Value.Track; @@ -62,7 +102,7 @@ namespace osu.Game.Graphics.Containers if (track != null && beatmap != null && track.IsRunning && track.Length > 0) { - currentTrackTime = track.CurrentTime + EarlyActivationMilliseconds; + currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds; timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); @@ -70,13 +110,15 @@ namespace osu.Game.Graphics.Containers IsBeatSyncedWithTrack = timingPoint?.BeatLength > 0; - if (timingPoint == null || !IsBeatSyncedWithTrack) + if (!IsBeatSyncedWithTrack) { - currentTrackTime = Clock.CurrentTime; + currentTrackTime = clock.CurrentTime; timingPoint = TimingControlPoint.DEFAULT; effectPoint = EffectControlPoint.DEFAULT; } + Debug.Assert(timingPoint != null); + double beatLength = timingPoint.BeatLength / Divisor; while (beatLength < MinimumBeatLength) @@ -103,15 +145,5 @@ namespace osu.Game.Graphics.Containers lastBeat = beatIndex; lastTimingPoint = timingPoint; } - - [BackgroundDependencyLoader] - private void load(IBindable beatmap) - { - Beatmap.BindTo(beatmap); - } - - protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) - { - } } } From cab8b941322b2731605778a914badf9388e5b67c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 14:36:37 +0900 Subject: [PATCH 1516/1621] Add failing test --- .../TestSceneBeatSyncedContainer.cs | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index f58f9fcaaf..ac1f88ad33 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -57,15 +58,28 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestFirstBeatAtFirstTimingPoint() { - AddStep("Set time before zero", () => - { - gameplayClockContainer.Seek(-1000); - }); + int? lastBeatIndex = null; + double? lastBpm = null; AddStep("bind event", () => { - beatContainer.NewBeat = (i, point, effectControlPoint, channelAmplitudes) => { }; + beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => + { + lastBeatIndex = i; + lastBpm = timingControlPoint.BPM; + }; }); + + AddStep("Set time before zero", () => + { + lastBeatIndex = null; + lastBpm = null; + gameplayClockContainer.Seek(-1000); + }); + + AddUntilStep("wait for trigger", () => lastBpm != null); + AddAssert("bpm is from beatmap", () => lastBpm != null&&Precision.AlmostEquals(lastBpm.Value, 128)); + AddAssert("beat index is less than zero", () => lastBeatIndex < 0); } private class BeatContainer : BeatSyncedContainer From 5ecf6511e6543300fdba03197b5ec53583db54be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 14:37:08 +0900 Subject: [PATCH 1517/1621] Fix default timing points being used if "track" is not running --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index cb26406d64..6d5f4d63ac 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -100,7 +100,7 @@ namespace osu.Game.Graphics.Containers beatmap = Beatmap.Value.Beatmap; } - if (track != null && beatmap != null && track.IsRunning && track.Length > 0) + if (track != null && beatmap != null && clock.IsRunning && track.Length > 0) { currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds; From c47ff1919c0075cb64b7e1539885f26b6adc012a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 14:56:49 +0900 Subject: [PATCH 1518/1621] Fix regression in idle behaviour and refactor further --- .../Containers/BeatSyncedContainer.cs | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 6d5f4d63ac..929a300831 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -21,6 +21,8 @@ namespace osu.Game.Graphics.Containers /// This container does not set its own clock to the source used for beat matching. /// This means that if the beat source clock is playing faster or slower, animations may unexpectedly overlap. /// Make sure this container's Clock is also set to the expected source (or within a parent element which provides this). + /// + /// This container will also trigger beat events when the beat matching clock is paused at 's BPM. /// public class BeatSyncedContainer : Container { @@ -53,6 +55,9 @@ namespace osu.Game.Graphics.Containers /// public double MinimumBeatLength { get; set; } + /// + /// Whether this container is currently tracking a beatmap's timing data. + /// protected bool IsBeatSyncedWithTrack { get; private set; } protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) @@ -84,35 +89,36 @@ namespace osu.Game.Graphics.Containers ITrack track = null; IBeatmap beatmap = null; - double currentTrackTime = 0; + TimingControlPoint timingPoint; + EffectControlPoint effectPoint; - TimingControlPoint timingPoint = null; - EffectControlPoint effectPoint = null; - - var clock = BeatSyncClock; + IClock clock = BeatSyncClock; if (clock == null) return; + double currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds; + if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded) { track = Beatmap.Value.Track; beatmap = Beatmap.Value.Beatmap; } - if (track != null && beatmap != null && clock.IsRunning && track.Length > 0) + IsBeatSyncedWithTrack = beatmap != null && clock.IsRunning && track?.Length > 0; + + if (IsBeatSyncedWithTrack) { - currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds; + Debug.Assert(beatmap != null); timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); } - - IsBeatSyncedWithTrack = timingPoint?.BeatLength > 0; - - if (!IsBeatSyncedWithTrack) + else { - currentTrackTime = clock.CurrentTime; + // this may be the case where the beat syncing clock has been paused. + // we still want to show an idle animation, so use this container's time instead. + currentTrackTime = Clock.CurrentTime; timingPoint = TimingControlPoint.DEFAULT; effectPoint = EffectControlPoint.DEFAULT; } From 77bfe700e091fd16af9b27edddfd987ec0094579 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 14:59:57 +0900 Subject: [PATCH 1519/1621] Add test coverage of idle beat --- .../TestSceneBeatSyncedContainer.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index ac1f88ad33..3ca6bf782a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -78,10 +78,33 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddUntilStep("wait for trigger", () => lastBpm != null); - AddAssert("bpm is from beatmap", () => lastBpm != null&&Precision.AlmostEquals(lastBpm.Value, 128)); + AddAssert("bpm is from beatmap", () => lastBpm != null && Precision.AlmostEquals(lastBpm.Value, 128)); AddAssert("beat index is less than zero", () => lastBeatIndex < 0); } + [Test] + public void TestIdleBeatOnPausedClock() + { + double? lastBpm = null; + + AddStep("bind event", () => + { + beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => lastBpm = timingControlPoint.BPM; + }); + + AddUntilStep("wait for trigger", () => lastBpm != null); + AddAssert("bpm is from beatmap", () => lastBpm != null && Precision.AlmostEquals(lastBpm.Value, 128)); + + AddStep("pause gameplay clock", () => + { + lastBpm = null; + gameplayClockContainer.Stop(); + }); + + AddUntilStep("wait for trigger", () => lastBpm != null); + AddAssert("bpm is from beatmap", () => lastBpm != null && Precision.AlmostEquals(lastBpm.Value, 60)); + } + private class BeatContainer : BeatSyncedContainer { private const int flash_layer_height = 150; From 98a1f40a982fe52d6482877c2850ab8cc13ab309 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 15:10:14 +0900 Subject: [PATCH 1520/1621] Ensure `EarlyActivationMilliseconds` is applied even in idle state --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 929a300831..55f694f17e 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -97,7 +97,7 @@ namespace osu.Game.Graphics.Containers if (clock == null) return; - double currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds; + double currentTrackTime = clock.CurrentTime; if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded) { @@ -123,7 +123,7 @@ namespace osu.Game.Graphics.Containers effectPoint = EffectControlPoint.DEFAULT; } - Debug.Assert(timingPoint != null); + currentTrackTime += EarlyActivationMilliseconds; double beatLength = timingPoint.BeatLength / Divisor; From d0fc25888683d7fc75e4da27c111acc77ad08364 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 15:11:42 +0900 Subject: [PATCH 1521/1621] Remove unused `OsuLogo.BeatMatching` --- osu.Game/Screens/Menu/OsuLogo.cs | 4 ---- osu.Game/Screens/OsuScreen.cs | 1 - 2 files changed, 5 deletions(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 283be913b0..a9376325cd 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -72,8 +72,6 @@ namespace osu.Game.Screens.Menu set => colourAndTriangles.FadeTo(value ? 1 : 0, transition_length, Easing.OutQuint); } - public bool BeatMatching = true; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => logoContainer.ReceivePositionalInputAt(screenSpacePos); public bool Ripple @@ -272,8 +270,6 @@ namespace osu.Game.Screens.Menu { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - if (!BeatMatching) return; - lastBeatIndex = beatIndex; var beatLength = timingPoint.BeatLength; diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index aeb51813e4..c3b2612e79 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -242,7 +242,6 @@ namespace osu.Game.Screens logo.Anchor = Anchor.TopLeft; logo.Origin = Anchor.Centre; logo.RelativePositionAxes = Axes.Both; - logo.BeatMatching = true; logo.Triangles = true; logo.Ripple = true; } From 3197f599bb2a9d5a1f516416293d5ac0ab0e636b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 16:01:46 +0900 Subject: [PATCH 1522/1621] Add failing test showing `OnNewBeat` can execute far away from an actual beat --- .../TestSceneBeatSyncedContainer.cs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 3ca6bf782a..5bfaf35bf1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -56,7 +56,34 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestFirstBeatAtFirstTimingPoint() + public void TestSeekBackDoesntPlayMidBeat() + { + int? lastBeatIndex = null; + double? lastActuationTime = null; + TimingControlPoint lastTimingPoint = null; + + AddStep("bind event", () => + { + beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => + { + lastActuationTime = gameplayClockContainer.CurrentTime; + lastTimingPoint = timingControlPoint; + lastBeatIndex = i; + }; + }); + + AddStep("Set time before zero", () => + { + lastBeatIndex = null; + gameplayClockContainer.Seek(-1000); + }); + + AddUntilStep("wait for trigger", () => lastBeatIndex != null); + AddAssert("trigger is near beat length", () => lastActuationTime != null && lastBeatIndex != null && Precision.AlmostEquals(lastTimingPoint.Time + lastBeatIndex.Value * lastTimingPoint.BeatLength, lastActuationTime.Value, 32)); + } + + [Test] + public void TestNegativeBeatsStillUsingBeatmapTiming() { int? lastBeatIndex = null; double? lastBpm = null; From b6996d647e9cf62232412e350eb4cc3d98f51680 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 16:13:13 +0900 Subject: [PATCH 1523/1621] Add ability to disable mistimed event firings --- .../TestSceneBeatSyncedContainer.cs | 31 ++++++++++++++----- .../Containers/BeatSyncedContainer.cs | 23 ++++++++++++-- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 5bfaf35bf1..2c5433e4aa 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface [TestFixture] public class TestSceneBeatSyncedContainer : OsuTestScene { - private BeatContainer beatContainer; + private TestBeatSyncedContainer beatContainer; private MasterGameplayClockContainer gameplayClockContainer; @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.UserInterface { gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0) { - Child = beatContainer = new BeatContainer + Child = beatContainer = new TestBeatSyncedContainer { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, @@ -55,13 +55,16 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Start playback", () => gameplayClockContainer.Start()); } - [Test] - public void TestSeekBackDoesntPlayMidBeat() + [TestCase(false)] + [TestCase(true)] + public void TestDisallowMistimedEventFiring(bool allowMistimed) { int? lastBeatIndex = null; double? lastActuationTime = null; TimingControlPoint lastTimingPoint = null; + AddStep("set mistimed to disallow", () => beatContainer.AllowMistimedEventFiring = allowMistimed); + AddStep("bind event", () => { beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => @@ -79,7 +82,15 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddUntilStep("wait for trigger", () => lastBeatIndex != null); - AddAssert("trigger is near beat length", () => lastActuationTime != null && lastBeatIndex != null && Precision.AlmostEquals(lastTimingPoint.Time + lastBeatIndex.Value * lastTimingPoint.BeatLength, lastActuationTime.Value, 32)); + + if (!allowMistimed) + { + AddAssert("trigger is near beat length", () => lastActuationTime != null && lastBeatIndex != null && Precision.AlmostEquals(lastTimingPoint.Time + lastBeatIndex.Value * lastTimingPoint.BeatLength, lastActuationTime.Value, BeatSyncedContainer.MISTIMED_ALLOWANCE)); + } + else + { + AddAssert("trigger is not near beat length", () => lastActuationTime != null && lastBeatIndex != null && !Precision.AlmostEquals(lastTimingPoint.Time + lastBeatIndex.Value * lastTimingPoint.BeatLength, lastActuationTime.Value, BeatSyncedContainer.MISTIMED_ALLOWANCE)); + } } [Test] @@ -132,10 +143,16 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("bpm is from beatmap", () => lastBpm != null && Precision.AlmostEquals(lastBpm.Value, 60)); } - private class BeatContainer : BeatSyncedContainer + private class TestBeatSyncedContainer : BeatSyncedContainer { private const int flash_layer_height = 150; + public new bool AllowMistimedEventFiring + { + get => base.AllowMistimedEventFiring; + set => base.AllowMistimedEventFiring = value; + } + private readonly InfoString timingPointCount; private readonly InfoString currentTimingPoint; private readonly InfoString beatCount; @@ -148,7 +165,7 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly Box flashLayer; - public BeatContainer() + public TestBeatSyncedContainer() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 55f694f17e..78ba716cca 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Audio.Track; @@ -35,6 +36,19 @@ namespace osu.Game.Graphics.Containers /// protected double EarlyActivationMilliseconds; + /// + /// While this container automatically applied an animation delay (meaning any animations inside a implementation will + /// always be correctly timed), the event itself can potentially fire away from the related beat. + /// + /// By setting this to false, cases where the event is to be fired more than from the related beat will be skipped. + /// + protected bool AllowMistimedEventFiring = true; + + /// + /// The maximum deviance from the actual beat that an can fire when is set to false. + /// + public const double MISTIMED_ALLOWANCE = 16; + /// /// The time in milliseconds until the next beat. /// @@ -145,8 +159,13 @@ namespace osu.Game.Graphics.Containers if (timingPoint == lastTimingPoint && beatIndex == lastBeat) return; - using (BeginDelayedSequence(-TimeSinceLastBeat)) - OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? ChannelAmplitudes.Empty); + // as this event is sometimes used for sound triggers where `BeginDelayedSequence` has no effect, avoid firing it if too far away from the beat. + // this can happen after a seek operation. + if (AllowMistimedEventFiring || Math.Abs(TimeSinceLastBeat) < MISTIMED_ALLOWANCE) + { + using (BeginDelayedSequence(-TimeSinceLastBeat)) + OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? ChannelAmplitudes.Empty); + } lastBeat = beatIndex; lastTimingPoint = timingPoint; From c38590f1ff11469e024fa94b56837b8a3b92a0bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 16:42:07 +0900 Subject: [PATCH 1524/1621] Use a slightly more appropriate metronome sound --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 8f9ff81808..3e08cfcd7a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -353,23 +353,24 @@ namespace osu.Game.Rulesets.Osu.Mods Divisor = 1; } + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + sample = new PausableSkinnableSound(new SampleInfo("Gameplay/catch-banana")) + }; + } + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); if (!IsBeatSyncedWithTrack) return; + sample.Frequency.Value = beatIndex % (int)timingPoint.TimeSignature == 0 ? 1 : 0.5f; sample?.Play(); } - - [BackgroundDependencyLoader] - private void load() - { - InternalChildren = new Drawable[] - { - sample = new PausableSkinnableSound(new SampleInfo("Gameplay/nightcore-hat")) // todo: use another sample - }; - } } #endregion From ea87869753a7b99a94f1b9fbb1e3a42a5bacc4a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 16:50:55 +0900 Subject: [PATCH 1525/1621] Fix metronome playing during intro time --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 3e08cfcd7a..5fa3bc4520 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -341,15 +341,18 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - drawableRuleset.Overlays.Add(new TargetBeatContainer()); + drawableRuleset.Overlays.Add(new TargetBeatContainer(drawableRuleset.Beatmap.HitObjects.First().StartTime)); } public class TargetBeatContainer : BeatSyncedContainer { + private readonly double firstHitTime; + private PausableSkinnableSound sample; - public TargetBeatContainer() + public TargetBeatContainer(double firstHitTime) { + this.firstHitTime = firstHitTime; Divisor = 1; } @@ -368,8 +371,15 @@ namespace osu.Game.Rulesets.Osu.Mods if (!IsBeatSyncedWithTrack) return; - sample.Frequency.Value = beatIndex % (int)timingPoint.TimeSignature == 0 ? 1 : 0.5f; - sample?.Play(); + int timeSignature = (int)timingPoint.TimeSignature; + + // play metronome from one measure before the first object. + // TODO: Use BeatSyncClock from https://github.com/ppy/osu/pull/13894. + if (Clock.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature) + return; + + sample.Frequency.Value = beatIndex % timeSignature == 0 ? 1 : 0.5f; + sample.Play(); } } From efdc8fa8a6cfdebabf5b97ab663e910c9eb3d0f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Jul 2021 19:08:12 +0900 Subject: [PATCH 1526/1621] Fix incorrect step name Co-authored-by: Henry Lin --- .../Visual/UserInterface/TestSceneBeatSyncedContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 2c5433e4aa..6bd5c7bcd8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.UserInterface double? lastActuationTime = null; TimingControlPoint lastTimingPoint = null; - AddStep("set mistimed to disallow", () => beatContainer.AllowMistimedEventFiring = allowMistimed); + AddStep($"set mistimed to {(allowMistimed ? "allowed" : "disallowed")}", () => beatContainer.AllowMistimedEventFiring = allowMistimed); AddStep("bind event", () => { From fa8e5013c5b51487230d4fd11d7b96f73712bb3e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Jul 2021 18:22:34 +0900 Subject: [PATCH 1527/1621] Adjust mania speed range --- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index e497646a13..13b107457e 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -33,12 +33,12 @@ namespace osu.Game.Rulesets.Mania.UI /// /// The minimum time range. This occurs at a of 40. /// - public const double MIN_TIME_RANGE = 340; + public const double MIN_TIME_RANGE = 290; /// /// The maximum time range. This occurs at a of 1. /// - public const double MAX_TIME_RANGE = 13720; + public const double MAX_TIME_RANGE = 11485; protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield; From 8541e73fc1675742c7e720f988a93706662e9315 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 16 Jul 2021 16:26:15 +0700 Subject: [PATCH 1528/1621] use text flow in markdown image caption --- .../Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs index 1a4f6087c7..501e00bc00 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Wiki.Markdown public class WikiMarkdownImageBlock : FillFlowContainer { [Resolved] - private IMarkdownTextComponent parentTextComponent { get; set; } + private IMarkdownTextFlowComponent parentFlowComponent { get; set; } private readonly LinkInline linkInline; @@ -31,16 +31,20 @@ namespace osu.Game.Overlays.Wiki.Markdown [BackgroundDependencyLoader] private void load() { + MarkdownTextFlowContainer textFlow; + Children = new Drawable[] { new BlockMarkdownImage(linkInline), - parentTextComponent.CreateSpriteText().With(t => + textFlow = parentFlowComponent.CreateTextFlow().With(t => { - t.Text = linkInline.Title; t.Anchor = Anchor.TopCentre; t.Origin = Anchor.TopCentre; + t.TextAnchor = Anchor.TopCentre; }), }; + + textFlow.AddText(linkInline.Title); } private class BlockMarkdownImage : WikiMarkdownImage From 3ac58d6838c573bc9de9fe739e471f99e08bce59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Jul 2021 19:32:31 +0900 Subject: [PATCH 1529/1621] Fix min/max values not being passed to inner time range --- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 13b107457e..614a7b00c7 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.UI protected override ScrollVisualisationMethod VisualisationMethod => scrollMethod; private readonly Bindable configDirection = new Bindable(); - private readonly Bindable configTimeRange = new BindableDouble(); + private readonly BindableDouble configTimeRange = new BindableDouble(); // Stores the current speed adjustment active in gameplay. private readonly Track speedAdjustmentTrack = new TrackVirtual(0); @@ -103,6 +103,8 @@ namespace osu.Game.Rulesets.Mania.UI configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange); + TimeRange.MinValue = configTimeRange.MinValue; + TimeRange.MaxValue = configTimeRange.MaxValue; } protected override void AdjustScrollSpeed(int amount) From 05234d6c30c76b2a768bb8b63ded2c958a44ed3c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Jul 2021 19:32:39 +0900 Subject: [PATCH 1530/1621] Fix mania hitobjects not appearing early enough --- .../Objects/Drawables/DrawableManiaHitObject.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 3ec68bfb56..5aff4e200b 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -22,6 +22,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected readonly IBindable Direction = new Bindable(); + // Leaving the default (10s) makes hitobjects not appear, as this offset is used for the initial state transforms. + // Calculated as DrawableManiaRuleset.MAX_TIME_RANGE + some additional allowance for velocity < 1. + protected override double InitialLifetimeOffset => 30000; + [Resolved(canBeNull: true)] private ManiaPlayfield playfield { get; set; } From b3d89254a96774ae0095aff468950d6141458247 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Jul 2021 23:03:53 +0900 Subject: [PATCH 1531/1621] Bump `LocalisationAnalyser` for new key generation --- 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 b2f1d6507f..152ba55e08 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 7df7137c8867cd2761dbd59b08b166eba9bed8c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Jul 2021 23:03:44 +0900 Subject: [PATCH 1532/1621] Add localisation support for remaining input settings sections --- .../Localisation/BindingSettingsStrings.cs | 29 +++++++++ osu.Game/Localisation/CommonStrings.cs | 17 +++++- .../Localisation/TabletSettingsStrings.cs | 59 +++++++++++++++++++ .../Sections/Input/BindingSettings.cs | 7 ++- .../Sections/Input/RotationPresetButtons.cs | 2 +- .../Settings/Sections/Input/TabletSettings.cs | 25 ++++---- 6 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Localisation/BindingSettingsStrings.cs create mode 100644 osu.Game/Localisation/TabletSettingsStrings.cs diff --git a/osu.Game/Localisation/BindingSettingsStrings.cs b/osu.Game/Localisation/BindingSettingsStrings.cs new file mode 100644 index 0000000000..ad4a650a1f --- /dev/null +++ b/osu.Game/Localisation/BindingSettingsStrings.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class BindingSettingsStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.BindingSettings"; + + /// + /// "Shortcut and gameplay bindings" + /// + public static LocalisableString ShortcutAndGameplayBindings => new TranslatableString(getKey(@"shortcut_and_gameplay_bindings"), @"Shortcut and gameplay bindings"); + + /// + /// "Configure" + /// + public static LocalisableString Configure => new TranslatableString(getKey(@"configure"), @"Configure"); + + /// + /// "change global shortcut keys and gameplay bindings" + /// + public static LocalisableString ChangeBindingsButton => new TranslatableString(getKey(@"change_bindings_button"), @"change global shortcut keys and gameplay bindings"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index ad87986554..bf488d2590 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -14,6 +14,21 @@ namespace osu.Game.Localisation /// public static LocalisableString Cancel => new TranslatableString(getKey(@"cancel"), @"Cancel"); + /// + /// "Enabled" + /// + public static LocalisableString Enabled => new TranslatableString(getKey(@"enabled"), @"Enabled"); + + /// + /// "Width" + /// + public static LocalisableString Width => new TranslatableString(getKey(@"width"), @"Width"); + + /// + /// "Height" + /// + public static LocalisableString Height => new TranslatableString(getKey(@"height"), @"Height"); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} +} \ No newline at end of file diff --git a/osu.Game/Localisation/TabletSettingsStrings.cs b/osu.Game/Localisation/TabletSettingsStrings.cs new file mode 100644 index 0000000000..5bdca09e4a --- /dev/null +++ b/osu.Game/Localisation/TabletSettingsStrings.cs @@ -0,0 +1,59 @@ +// 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 TabletSettingsStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.TabletSettings"; + + /// + /// "Tablet" + /// + public static LocalisableString Tablet => new TranslatableString(getKey(@"tablet"), @"Tablet"); + + /// + /// "No tablet detected!" + /// + public static LocalisableString NoTabletDetected => new TranslatableString(getKey(@"no_tablet_detected"), @"No tablet detected!"); + + /// + /// "Reset to full area" + /// + public static LocalisableString ResetToFullArea => new TranslatableString(getKey(@"reset_to_full_area"), @"Reset to full area"); + + /// + /// "Conform to current game aspect ratio" + /// + public static LocalisableString ConformToCurrentGameAspectRatio => new TranslatableString(getKey(@"conform_to_current_game_aspect_ratio"), @"Conform to current game aspect ratio"); + + /// + /// "X Offset" + /// + public static LocalisableString XOffset => new TranslatableString(getKey(@"x_offset"), @"X Offset"); + + /// + /// "Y Offset" + /// + public static LocalisableString YOffset => new TranslatableString(getKey(@"y_offset"), @"Y Offset"); + + /// + /// "Rotation" + /// + public static LocalisableString Rotation => new TranslatableString(getKey(@"rotation"), @"Rotation"); + + /// + /// "Aspect Ratio" + /// + public static LocalisableString AspectRatio => new TranslatableString(getKey(@"aspect_ratio"), @"Aspect Ratio"); + + /// + /// "Lock aspect ratio" + /// + public static LocalisableString LockAspectRatio => new TranslatableString(getKey(@"lock_aspect_ratio"), @"Lock aspect ratio"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs index e68cd606e5..3227decc46 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs @@ -3,12 +3,13 @@ using osu.Framework.Graphics; using osu.Framework.Localisation; +using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Input { public class BindingSettings : SettingsSubsection { - protected override LocalisableString Header => "Shortcut and gameplay bindings"; + protected override LocalisableString Header => BindingSettingsStrings.ShortcutAndGameplayBindings; public BindingSettings(KeyBindingPanel keyConfig) { @@ -16,8 +17,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { new SettingsButton { - Text = "Configure", - TooltipText = "change global shortcut keys and gameplay bindings", + Text = BindingSettingsStrings.Configure, + TooltipText = BindingSettingsStrings.ChangeBindingsButton, Action = keyConfig.ToggleVisibility }, }; diff --git a/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs b/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs index 3e8da9f7d0..26610628d5 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input RelativeSizeAxes = Axes.X, Height = height, Width = 0.25f, - Text = $"{presetRotation}º", + Text = $@"{presetRotation}º", Action = () => tabletHandler.Rotation.Value = presetRotation, }); } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index e9ed3378a2..c7342c251d 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -11,6 +11,7 @@ using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics.Sprites; using osuTK; +using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Input { @@ -53,7 +54,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private OsuSpriteText noTabletMessage; - protected override LocalisableString Header => "Tablet"; + protected override LocalisableString Header => TabletSettingsStrings.Tablet; public TabletSettings(ITabletHandler tabletHandler) { @@ -67,14 +68,14 @@ namespace osu.Game.Overlays.Settings.Sections.Input { new SettingsCheckbox { - LabelText = "Enabled", + LabelText = CommonStrings.Enabled, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Current = tabletHandler.Enabled }, noTabletMessage = new OsuSpriteText { - Text = "No tablet detected!", + Text = TabletSettingsStrings.NoTabletDetected, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS } @@ -95,7 +96,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, new DangerousSettingsButton { - Text = "Reset to full area", + Text = TabletSettingsStrings.ResetToFullArea, Action = () => { aspectLock.Value = false; @@ -106,7 +107,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, new SettingsButton { - Text = "Conform to current game aspect ratio", + Text = TabletSettingsStrings.ConformToCurrentGameAspectRatio, Action = () => { forceAspectRatio((float)host.Window.ClientSize.Width / host.Window.ClientSize.Height); @@ -115,43 +116,43 @@ namespace osu.Game.Overlays.Settings.Sections.Input new SettingsSlider { TransferValueOnCommit = true, - LabelText = "X Offset", + LabelText = TabletSettingsStrings.XOffset, Current = offsetX }, new SettingsSlider { TransferValueOnCommit = true, - LabelText = "Y Offset", + LabelText = TabletSettingsStrings.YOffset, Current = offsetY }, new SettingsSlider { TransferValueOnCommit = true, - LabelText = "Rotation", + LabelText = TabletSettingsStrings.Rotation, Current = rotation }, new RotationPresetButtons(tabletHandler), new SettingsSlider { TransferValueOnCommit = true, - LabelText = "Aspect Ratio", + LabelText = TabletSettingsStrings.AspectRatio, Current = aspectRatio }, new SettingsCheckbox { - LabelText = "Lock aspect ratio", + LabelText = TabletSettingsStrings.LockAspectRatio, Current = aspectLock }, new SettingsSlider { TransferValueOnCommit = true, - LabelText = "Width", + LabelText = CommonStrings.Width, Current = sizeX }, new SettingsSlider { TransferValueOnCommit = true, - LabelText = "Height", + LabelText = CommonStrings.Height, Current = sizeY }, } From bda16f0fbc7c818ad22821e31625c8150b80877b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Jul 2021 23:51:31 +0900 Subject: [PATCH 1533/1621] Fix progression lines getting stuck after removing a match in ladder editor screen --- .../Screens/Ladder/Components/DrawableTournamentMatch.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs index 1c805bb42e..6937c69dbf 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs @@ -303,6 +303,15 @@ namespace osu.Game.Tournament.Screens.Ladder.Components Match.LosersProgression.Value = null; ladderInfo.Matches.Remove(Match); + + foreach (var m in ladderInfo.Matches) + { + if (m.Progression.Value == Match) + m.Progression.Value = null; + + if (m.LosersProgression.Value == Match) + m.LosersProgression.Value = null; + } } } } From 83ebbb7f8e990fe78f2311ad9d75970959979f9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 00:21:58 +0900 Subject: [PATCH 1534/1621] Allow the schedule screen to show even when a current match is not selected --- .../Screens/TestSceneScheduleScreen.cs | 6 + .../Screens/Schedule/ScheduleScreen.cs | 106 +++++++++--------- 2 files changed, 60 insertions(+), 52 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs index 0da8d1eb4a..bd1bacd549 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs @@ -28,6 +28,12 @@ namespace osu.Game.Tournament.Tests.Screens setMatchDate(TimeSpan.FromHours(3)); } + [Test] + public void TestNoCurrentMatch() + { + AddStep("Set null current match", () => Ladder.CurrentMatch.Value = null); + } + private void setMatchDate(TimeSpan relativeTime) // Humanizer cannot handle negative timespans. => AddStep($"start time is {relativeTime}", () => diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index c1d8c8ddd3..e08be65465 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -96,19 +96,18 @@ namespace osu.Game.Tournament.Screens.Schedule } }, }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); - currentMatch.BindValueChanged(matchChanged); currentMatch.BindTo(ladder.CurrentMatch); + currentMatch.BindValueChanged(matchChanged, true); } private void matchChanged(ValueChangedEvent match) { - if (match.NewValue == null) - { - mainContainer.Clear(); - return; - } - var upcoming = ladder.Matches.Where(p => !p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4); var conditionals = ladder .Matches.Where(p => !p.Completed.Value && (p.Team1.Value == null || p.Team2.Value == null) && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) @@ -117,6 +116,8 @@ namespace osu.Game.Tournament.Screens.Schedule upcoming = upcoming.Concat(conditionals); upcoming = upcoming.OrderBy(p => p.Date.Value).Take(8); + ScheduleContainer comingUpNext; + mainContainer.Child = new FillFlowContainer { RelativeSizeAxes = Axes.Both, @@ -153,57 +154,58 @@ namespace osu.Game.Tournament.Screens.Schedule } } }, - new ScheduleContainer("coming up next") + comingUpNext = new ScheduleContainer("coming up next") { RelativeSizeAxes = Axes.Both, Height = 0.25f, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(30), - Children = new Drawable[] - { - new ScheduleMatch(match.NewValue, false) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - new TournamentSpriteTextWithBackground(match.NewValue.Round.Value?.Name.Value) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(0.5f) - }, - new TournamentSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = match.NewValue.Team1.Value?.FullName + " vs " + match.NewValue.Team2.Value?.FullName, - Font = OsuFont.Torus.With(size: 24, weight: FontWeight.SemiBold) - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Children = new Drawable[] - { - new ScheduleMatchDate(match.NewValue.Date.Value) - { - Font = OsuFont.Torus.With(size: 24, weight: FontWeight.Regular) - } - } - }, - } - }, - } } } }; + + if (match.NewValue != null) + { + comingUpNext.Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(30), + Children = new Drawable[] + { + new ScheduleMatch(match.NewValue, false) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + new TournamentSpriteTextWithBackground(match.NewValue.Round.Value?.Name.Value) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(0.5f) + }, + new TournamentSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = match.NewValue.Team1.Value?.FullName + " vs " + match.NewValue.Team2.Value?.FullName, + Font = OsuFont.Torus.With(size: 24, weight: FontWeight.SemiBold) + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + new ScheduleMatchDate(match.NewValue.Date.Value) + { + Font = OsuFont.Torus.With(size: 24, weight: FontWeight.Regular) + } + } + }, + } + }; + } } public class ScheduleMatch : DrawableTournamentMatch From 71f74f0e9860bcca7054df6bf40ec67cabdd3e5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Jul 2021 23:34:28 +0900 Subject: [PATCH 1535/1621] Add warning message to screens which require a current match to be selected --- .../Screens/MapPool/MapPoolScreen.cs | 40 +++++++++---------- .../Screens/TournamentMatchScreen.cs | 34 ++++++++++++++++ 2 files changed, 54 insertions(+), 20 deletions(-) create mode 100644 osu.Game.Tournament/Screens/TournamentMatchScreen.cs diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 2c4fed8d86..d4292c5492 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -21,12 +21,10 @@ using osuTK.Input; namespace osu.Game.Tournament.Screens.MapPool { - public class MapPoolScreen : TournamentScreen + public class MapPoolScreen : TournamentMatchScreen { private readonly FillFlowContainer> mapFlows; - private readonly Bindable currentMatch = new Bindable(); - [Resolved(canBeNull: true)] private TournamentSceneManager sceneManager { get; set; } @@ -96,7 +94,7 @@ namespace osu.Game.Tournament.Screens.MapPool Action = reset }, new ControlPanel.Spacer(), - } + }, } }; } @@ -104,15 +102,12 @@ namespace osu.Game.Tournament.Screens.MapPool [BackgroundDependencyLoader] private void load(MatchIPCInfo ipc) { - currentMatch.BindValueChanged(matchChanged); - currentMatch.BindTo(LadderInfo.CurrentMatch); - ipc.Beatmap.BindValueChanged(beatmapChanged); } private void beatmapChanged(ValueChangedEvent beatmap) { - if (currentMatch.Value == null || currentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) < 2) + if (CurrentMatch.Value == null || CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) < 2) return; // if bans have already been placed, beatmap changes result in a selection being made autoamtically @@ -137,12 +132,12 @@ namespace osu.Game.Tournament.Screens.MapPool { const TeamColour roll_winner = TeamColour.Red; //todo: draw from match - var nextColour = (currentMatch.Value.PicksBans.LastOrDefault()?.Team ?? roll_winner) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; + var nextColour = (CurrentMatch.Value.PicksBans.LastOrDefault()?.Team ?? roll_winner) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; - if (pickType == ChoiceType.Ban && currentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= 2) + if (pickType == ChoiceType.Ban && CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= 2) setMode(pickColour, ChoiceType.Pick); else - setMode(nextColour, currentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= 2 ? ChoiceType.Pick : ChoiceType.Ban); + setMode(nextColour, CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= 2 ? ChoiceType.Pick : ChoiceType.Ban); } protected override bool OnMouseDown(MouseDownEvent e) @@ -156,11 +151,11 @@ namespace osu.Game.Tournament.Screens.MapPool addForBeatmap(map.Beatmap.OnlineBeatmapID.Value); else { - var existing = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == map.Beatmap.OnlineBeatmapID); + var existing = CurrentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == map.Beatmap.OnlineBeatmapID); if (existing != null) { - currentMatch.Value.PicksBans.Remove(existing); + CurrentMatch.Value.PicksBans.Remove(existing); setNextMode(); } } @@ -173,7 +168,7 @@ namespace osu.Game.Tournament.Screens.MapPool private void reset() { - currentMatch.Value.PicksBans.Clear(); + CurrentMatch.Value.PicksBans.Clear(); setNextMode(); } @@ -181,18 +176,18 @@ namespace osu.Game.Tournament.Screens.MapPool private void addForBeatmap(int beatmapId) { - if (currentMatch.Value == null) + if (CurrentMatch.Value == null) return; - if (currentMatch.Value.Round.Value.Beatmaps.All(b => b.BeatmapInfo.OnlineBeatmapID != beatmapId)) + if (CurrentMatch.Value.Round.Value.Beatmaps.All(b => b.BeatmapInfo.OnlineBeatmapID != beatmapId)) // don't attempt to add if the beatmap isn't in our pool return; - if (currentMatch.Value.PicksBans.Any(p => p.BeatmapID == beatmapId)) + if (CurrentMatch.Value.PicksBans.Any(p => p.BeatmapID == beatmapId)) // don't attempt to add if already exists. return; - currentMatch.Value.PicksBans.Add(new BeatmapChoice + CurrentMatch.Value.PicksBans.Add(new BeatmapChoice { Team = pickColour, Type = pickType, @@ -201,17 +196,22 @@ namespace osu.Game.Tournament.Screens.MapPool setNextMode(); - if (pickType == ChoiceType.Pick && currentMatch.Value.PicksBans.Any(i => i.Type == ChoiceType.Pick)) + if (pickType == ChoiceType.Pick && CurrentMatch.Value.PicksBans.Any(i => i.Type == ChoiceType.Pick)) { scheduledChange?.Cancel(); scheduledChange = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(GameplayScreen)); }, 10000); } } - private void matchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { + base.CurrentMatchChanged(match); + mapFlows.Clear(); + if (match.NewValue == null) + return; + int totalRows = 0; if (match.NewValue.Round.Value != null) diff --git a/osu.Game.Tournament/Screens/TournamentMatchScreen.cs b/osu.Game.Tournament/Screens/TournamentMatchScreen.cs new file mode 100644 index 0000000000..5f00036653 --- /dev/null +++ b/osu.Game.Tournament/Screens/TournamentMatchScreen.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.Screens +{ + public abstract class TournamentMatchScreen : TournamentScreen + { + protected readonly Bindable CurrentMatch = new Bindable(); + private WarningBox noMatchWarning; + + protected override void LoadComplete() + { + base.LoadComplete(); + + CurrentMatch.BindTo(LadderInfo.CurrentMatch); + CurrentMatch.BindValueChanged(CurrentMatchChanged, true); + } + + protected virtual void CurrentMatchChanged(ValueChangedEvent match) + { + if (match.NewValue == null) + { + AddInternal(noMatchWarning = new WarningBox("Choose a match first from the brackets screen")); + return; + } + + noMatchWarning?.Expire(); + noMatchWarning = null; + } + } +} From 0a13e033eaa6907126fa852eb7dc71a8e587bc12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Jul 2021 23:59:16 +0900 Subject: [PATCH 1536/1621] Move height warning to bottom of screen to avoid overlap --- osu.Game.Tournament/TournamentGame.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 87e23e3404..cd0e601a2f 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -97,7 +97,12 @@ namespace osu.Game.Tournament }, } }, - heightWarning = new WarningBox("Please make the window wider"), + heightWarning = new WarningBox("Please make the window wider") + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding(20), + }, new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, From e8595871ded0ad53f3f3caf6f79f00a952523a5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 00:14:48 +0900 Subject: [PATCH 1537/1621] Update remaining screens to also show the warning message --- .../Screens/BeatmapInfoScreen.cs | 2 +- .../Screens/Gameplay/GameplayScreen.cs | 29 ++++++++++--------- .../Screens/Showcase/ShowcaseScreen.cs | 8 +++++ .../Screens/TeamIntro/SeedingScreen.cs | 22 +++++++------- .../Screens/TeamIntro/TeamIntroScreen.cs | 16 ++++------ .../Screens/TeamWin/TeamWinScreen.cs | 19 ++++++------ 6 files changed, 52 insertions(+), 44 deletions(-) diff --git a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs index 0a3163ef43..50498304ca 100644 --- a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs +++ b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs @@ -11,7 +11,7 @@ using osu.Game.Tournament.IPC; namespace osu.Game.Tournament.Screens { - public abstract class BeatmapInfoScreen : TournamentScreen + public abstract class BeatmapInfoScreen : TournamentMatchScreen { protected readonly SongBar SongBar; diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 6e4c6784c8..f61506d7f2 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -24,8 +24,6 @@ namespace osu.Game.Tournament.Screens.Gameplay { private readonly BindableBool warmup = new BindableBool(); - private readonly Bindable currentMatch = new Bindable(); - public readonly Bindable State = new Bindable(); private OsuButton warmupButton; private MatchIPCInfo ipc; @@ -131,14 +129,6 @@ namespace osu.Game.Tournament.Screens.Gameplay ladder.ChromaKeyWidth.BindValueChanged(width => chroma.Width = width.NewValue, true); - currentMatch.BindValueChanged(m => - { - warmup.Value = m.NewValue.Team1Score.Value + m.NewValue.Team2Score.Value == 0; - scheduledOperation?.Cancel(); - }); - - currentMatch.BindTo(ladder.CurrentMatch); - warmup.BindValueChanged(w => { warmupButton.Alpha = !w.NewValue ? 0.5f : 1; @@ -146,6 +136,17 @@ namespace osu.Game.Tournament.Screens.Gameplay }, true); } + protected override void CurrentMatchChanged(ValueChangedEvent match) + { + base.CurrentMatchChanged(match); + + if (match.NewValue == null) + return; + + warmup.Value = match.NewValue.Team1Score.Value + match.NewValue.Team2Score.Value == 0; + scheduledOperation?.Cancel(); + } + private ScheduledDelegate scheduledOperation; private MatchScoreDisplay scoreDisplay; @@ -161,9 +162,9 @@ namespace osu.Game.Tournament.Screens.Gameplay if (warmup.Value) return; if (ipc.Score1.Value > ipc.Score2.Value) - currentMatch.Value.Team1Score.Value++; + CurrentMatch.Value.Team1Score.Value++; else - currentMatch.Value.Team2Score.Value++; + CurrentMatch.Value.Team2Score.Value++; } scheduledOperation?.Cancel(); @@ -198,9 +199,9 @@ namespace osu.Game.Tournament.Screens.Gameplay // we should automatically proceed after a short delay if (lastState == TourneyState.Ranking && !warmup.Value) { - if (currentMatch.Value?.Completed.Value == true) + if (CurrentMatch.Value?.Completed.Value == true) scheduledOperation = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(TeamWinScreen)); }, delay_before_progression); - else if (currentMatch.Value?.Completed.Value == false) + else if (CurrentMatch.Value?.Completed.Value == false) scheduledOperation = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(MapPoolScreen)); }, delay_before_progression); } diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index 9785b7e647..32d458e191 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Tournament.Components; using osu.Framework.Graphics.Shapes; +using osu.Game.Tournament.Models; using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Showcase @@ -39,5 +41,11 @@ namespace osu.Game.Tournament.Screens.Showcase } }); } + + protected override void CurrentMatchChanged(ValueChangedEvent match) + { + // showcase screen doesn't care about a match being selected. + // base call intentionally omitted to not show match warning. + } } } diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 4f66d89b7f..71aed69738 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -18,12 +18,10 @@ using osuTK; namespace osu.Game.Tournament.Screens.TeamIntro { - public class SeedingScreen : TournamentScreen, IProvideVideo + public class SeedingScreen : TournamentMatchScreen, IProvideVideo { private Container mainContainer; - private readonly Bindable currentMatch = new Bindable(); - private readonly Bindable currentTeam = new Bindable(); [BackgroundDependencyLoader] @@ -50,13 +48,13 @@ namespace osu.Game.Tournament.Screens.TeamIntro { RelativeSizeAxes = Axes.X, Text = "Show first team", - Action = () => currentTeam.Value = currentMatch.Value.Team1.Value, + Action = () => currentTeam.Value = CurrentMatch.Value.Team1.Value, }, new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Show second team", - Action = () => currentTeam.Value = currentMatch.Value.Team2.Value, + Action = () => currentTeam.Value = CurrentMatch.Value.Team2.Value, }, new SettingsTeamDropdown(LadderInfo.Teams) { @@ -67,9 +65,6 @@ namespace osu.Game.Tournament.Screens.TeamIntro } }; - currentMatch.BindValueChanged(matchChanged); - currentMatch.BindTo(LadderInfo.CurrentMatch); - currentTeam.BindValueChanged(teamChanged, true); } @@ -84,8 +79,15 @@ namespace osu.Game.Tournament.Screens.TeamIntro showTeam(team.NewValue); } - private void matchChanged(ValueChangedEvent match) => - currentTeam.Value = currentMatch.Value.Team1.Value; + protected override void CurrentMatchChanged(ValueChangedEvent match) + { + base.CurrentMatchChanged(match); + + if (match.NewValue == null) + return; + + currentTeam.Value = match.NewValue.Team1.Value; + } private void showTeam(TournamentTeam team) { diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index 6c2848897b..74957cbca5 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -12,12 +12,10 @@ using osuTK; namespace osu.Game.Tournament.Screens.TeamIntro { - public class TeamIntroScreen : TournamentScreen, IProvideVideo + public class TeamIntroScreen : TournamentMatchScreen, IProvideVideo { private Container mainContainer; - private readonly Bindable currentMatch = new Bindable(); - [BackgroundDependencyLoader] private void load(Storage storage) { @@ -35,18 +33,16 @@ namespace osu.Game.Tournament.Screens.TeamIntro RelativeSizeAxes = Axes.Both, } }; - - currentMatch.BindValueChanged(matchChanged); - currentMatch.BindTo(LadderInfo.CurrentMatch); } - private void matchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { + base.CurrentMatchChanged(match); + + mainContainer.Clear(); + if (match.NewValue == null) - { - mainContainer.Clear(); return; - } const float y_flag_offset = 292; diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 7ca262a2e8..ebe2908b74 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -13,11 +13,10 @@ using osuTK; namespace osu.Game.Tournament.Screens.TeamWin { - public class TeamWinScreen : TournamentScreen, IProvideVideo + public class TeamWinScreen : TournamentMatchScreen, IProvideVideo { private Container mainContainer; - private readonly Bindable currentMatch = new Bindable(); private readonly Bindable currentCompleted = new Bindable(); private TourneyVideo blueWinVideo; @@ -48,17 +47,19 @@ namespace osu.Game.Tournament.Screens.TeamWin } }; - currentMatch.BindValueChanged(matchChanged); - currentMatch.BindTo(ladder.CurrentMatch); - currentCompleted.BindValueChanged(_ => update()); } - private void matchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { - currentCompleted.UnbindBindings(); - currentCompleted.BindTo(match.NewValue.Completed); + base.CurrentMatchChanged(match); + currentCompleted.UnbindBindings(); + + if (match.NewValue == null) + return; + + currentCompleted.BindTo(match.NewValue.Completed); update(); } @@ -66,7 +67,7 @@ namespace osu.Game.Tournament.Screens.TeamWin private void update() => Schedule(() => { - var match = currentMatch.Value; + var match = CurrentMatch.Value; if (match.Winner == null) { From 7a671754f2c067b38ab8b4515fbd718cbcf8228e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 02:29:31 +0900 Subject: [PATCH 1538/1621] Change `RadioButton`'s `object` to a `string` --- .../Edit/Components/RadioButtons/RadioButton.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs index dcf5f8a788..c3ffd8add0 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons /// /// The item related to this button. /// - public object Item; + public string Item; /// /// A function which creates a drawable icon to represent this item. If null, a sane default should be used. @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons private readonly Action action; - public RadioButton(object item, Action action, Func createIcon = null) + public RadioButton(string item, Action action, Func createIcon = null) { Item = item; CreateIcon = createIcon; @@ -34,13 +34,6 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons Selected = new BindableBool(); } - public RadioButton(string item) - : this(item, null) - { - Item = item; - action = null; - } - /// /// Selects this . /// From 3ae5f6707abe50ffa9c4ba9bae2a359a0dc49468 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 02:30:00 +0900 Subject: [PATCH 1539/1621] Expose whether an `EditorBeatmap` has timing present or not via bindable --- osu.Game/Screens/Edit/EditorBeatmap.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index be53abbd55..7de98e5e85 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -46,12 +46,22 @@ namespace osu.Game.Screens.Edit public readonly IBeatmap PlayableBeatmap; + /// + /// Whether at least one timing control point is present and providing timing information. + /// + public IBindable HasTiming => hasTiming; + + private readonly Bindable hasTiming = new Bindable(); + [CanBeNull] public readonly ISkin BeatmapSkin; [Resolved] private BindableBeatDivisor beatDivisor { get; set; } + [Resolved] + private EditorClock editorClock { get; set; } + private readonly IBeatmapProcessor beatmapProcessor; private readonly Dictionary> startTimeBindables = new Dictionary>(); @@ -238,6 +248,8 @@ namespace osu.Game.Screens.Edit if (batchPendingUpdates.Count > 0) UpdateState(); + + hasTiming.Value = !ReferenceEquals(ControlPointInfo.TimingPointAt(editorClock.CurrentTime), TimingControlPoint.DEFAULT); } protected override void UpdateState() From eac9b1ec7e32eee5998cf4ff95cc464e04375eb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 02:30:13 +0900 Subject: [PATCH 1540/1621] Disable toolbox composition buttons when beatmap is not timed --- .../Editing/TestSceneHitObjectComposer.cs | 105 ++++++++++++++---- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 +- .../RadioButtons/DrawableRadioButton.cs | 27 +++-- 3 files changed, 103 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index 7ca24346aa..f75c976f56 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -2,17 +2,24 @@ // 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; -using osu.Framework.Timing; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Compose.Components; using osuTK; namespace osu.Game.Tests.Visual.Editing @@ -20,37 +27,89 @@ namespace osu.Game.Tests.Visual.Editing [TestFixture] public class TestSceneHitObjectComposer : EditorClockTestScene { - [BackgroundDependencyLoader] - private void load() + private OsuHitObjectComposer hitObjectComposer; + private EditorBeatmapContainer editorBeatmapContainer; + + private EditorBeatmap editorBeatmap => editorBeatmapContainer.EditorBeatmap; + + [SetUpSteps] + public void SetUpSteps() { - Beatmap.Value = CreateWorkingBeatmap(new Beatmap + AddStep("create beatmap", () => { - HitObjects = new List + Beatmap.Value = CreateWorkingBeatmap(new Beatmap { - new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }, - new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }, - new Slider + HitObjects = new List { - Position = new Vector2(128, 256), - Path = new SliderPath(PathType.Linear, new[] + new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }, + new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }, + new Slider { - Vector2.Zero, - new Vector2(216, 0), - }), - Scale = 0.5f, - } - }, + Position = new Vector2(128, 256), + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(216, 0), + }), + Scale = 0.5f, + } + }, + }); }); - var editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); + AddStep("Create composer", () => + { + Child = editorBeatmapContainer = new EditorBeatmapContainer(Beatmap.Value) + { + Child = hitObjectComposer = new OsuHitObjectComposer(new OsuRuleset()) + }; + }); + } - var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - Dependencies.CacheAs(clock); - Dependencies.CacheAs(clock); - Dependencies.CacheAs(editorBeatmap); - Dependencies.CacheAs(editorBeatmap); + [Test] + public void TestPlacementOnlyWorksWithTiming() + { + AddStep("clear all control points", () => editorBeatmap.ControlPointInfo.Clear()); - Child = new OsuHitObjectComposer(new OsuRuleset()); + AddAssert("Tool is selection", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is SelectTool); + AddAssert("Hitcircle button not clickable", () => !hitObjectComposer.ChildrenOfType().First(d => d.Button.Item == "HitCircle").Enabled.Value); + AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); + AddAssert("Hitcircle button is clickable", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Item == "HitCircle").Enabled.Value); + AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Item == "HitCircle").Click()); + AddAssert("Tool changed", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is HitCircleCompositionTool); + } + + public class EditorBeatmapContainer : Container + { + private readonly WorkingBeatmap working; + + public EditorBeatmap EditorBeatmap { get; private set; } + + public EditorBeatmapContainer(WorkingBeatmap working) + { + this.working = working; + + RelativeSizeAxes = Axes.Both; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + EditorBeatmap = new EditorBeatmap(working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); + + dependencies.CacheAs(EditorBeatmap); + dependencies.CacheAs(EditorBeatmap); + + return dependencies; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(EditorBeatmap); + } } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index a7005954b2..c6c112bec8 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -219,7 +219,8 @@ namespace osu.Game.Rulesets.Edit if (item != null) { - item.Select(); + if (!item.Selected.Disabled) + item.Select(); return true; } } diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 1f608d28fd..79468f0ab0 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -5,9 +5,11 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -16,24 +18,28 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.RadioButtons { - public class DrawableRadioButton : OsuButton + public class DrawableRadioButton : OsuButton, IHasTooltip { /// /// Invoked when this has been selected. /// public Action Selected; + public readonly RadioButton Button; + private Color4 defaultBackgroundColour; private Color4 defaultBubbleColour; private Color4 selectedBackgroundColour; private Color4 selectedBubbleColour; private Drawable icon; - private readonly RadioButton button; + + [Resolved(canBeNull: true)] + private EditorBeatmap editorBeatmap { get; set; } public DrawableRadioButton(RadioButton button) { - this.button = button; + this.Button = button; Text = button.Item.ToString(); Action = button.Select; @@ -57,7 +63,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons Colour = Color4.Black.Opacity(0.5f) }; - Add(icon = (button.CreateIcon?.Invoke() ?? new Circle()).With(b => + Add(icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b => { b.Blending = BlendingParameters.Additive; b.Anchor = Anchor.CentreLeft; @@ -71,13 +77,16 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons { base.LoadComplete(); - button.Selected.ValueChanged += selected => + Button.Selected.ValueChanged += selected => { updateSelectionState(); if (selected.NewValue) - Selected?.Invoke(button); + Selected?.Invoke(Button); }; + editorBeatmap?.HasTiming.BindValueChanged(hasTiming => Button.Selected.Disabled = !hasTiming.NewValue, true); + + Button.Selected.BindDisabledChanged(disabled => Enabled.Value = !disabled, true); updateSelectionState(); } @@ -86,8 +95,8 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons if (!IsLoaded) return; - BackgroundColour = button.Selected.Value ? selectedBackgroundColour : defaultBackgroundColour; - icon.Colour = button.Selected.Value ? selectedBubbleColour : defaultBubbleColour; + BackgroundColour = Button.Selected.Value ? selectedBackgroundColour : defaultBackgroundColour; + icon.Colour = Button.Selected.Value ? selectedBubbleColour : defaultBubbleColour; } protected override SpriteText CreateText() => new OsuSpriteText @@ -97,5 +106,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons Anchor = Anchor.CentreLeft, X = 40f }; + + public LocalisableString TooltipText => Enabled.Value ? string.Empty : "Add at least one timing point first!"; } } From 50eed26bd1addd0f929db1b0a55609e7e7cc8b97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 02:30:49 +0900 Subject: [PATCH 1541/1621] Rename radio button `item` to `label` --- osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs | 6 +++--- .../Edit/Components/RadioButtons/DrawableRadioButton.cs | 4 ++-- .../Screens/Edit/Components/RadioButtons/RadioButton.cs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index f75c976f56..67c37413ed 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -72,10 +72,10 @@ namespace osu.Game.Tests.Visual.Editing AddStep("clear all control points", () => editorBeatmap.ControlPointInfo.Clear()); AddAssert("Tool is selection", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is SelectTool); - AddAssert("Hitcircle button not clickable", () => !hitObjectComposer.ChildrenOfType().First(d => d.Button.Item == "HitCircle").Enabled.Value); + AddAssert("Hitcircle button not clickable", () => !hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Enabled.Value); AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); - AddAssert("Hitcircle button is clickable", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Item == "HitCircle").Enabled.Value); - AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Item == "HitCircle").Click()); + AddAssert("Hitcircle button is clickable", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Enabled.Value); + AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Click()); AddAssert("Tool changed", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is HitCircleCompositionTool); } diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 79468f0ab0..bbe77eaa07 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -39,9 +39,9 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons public DrawableRadioButton(RadioButton button) { - this.Button = button; + Button = button; - Text = button.Item.ToString(); + Text = button.Label; Action = button.Select; RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs index c3ffd8add0..ca79dd15d7 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons /// /// The item related to this button. /// - public string Item; + public string Label; /// /// A function which creates a drawable icon to represent this item. If null, a sane default should be used. @@ -26,9 +26,9 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons private readonly Action action; - public RadioButton(string item, Action action, Func createIcon = null) + public RadioButton(string label, Action action, Func createIcon = null) { - Item = item; + Label = label; CreateIcon = createIcon; this.action = action; Selected = new BindableBool(); From db4d64effb1f208b528af2ff4d0bd5a1954d86f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 14:29:18 +0900 Subject: [PATCH 1542/1621] Rename incorrect step --- .../Visual/UserInterface/TestSceneBeatSyncedContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 6bd5c7bcd8..6b56f339d8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddUntilStep("wait for trigger", () => lastBpm != null); - AddAssert("bpm is from beatmap", () => lastBpm != null && Precision.AlmostEquals(lastBpm.Value, 60)); + AddAssert("bpm is default", () => lastBpm != null && Precision.AlmostEquals(lastBpm.Value, 60)); } private class TestBeatSyncedContainer : BeatSyncedContainer From 23ed77f2c6f10135e3b849e5adf96008372b9cd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 14:33:02 +0900 Subject: [PATCH 1543/1621] Fix test failure under visual tests due to double firing --- .../UserInterface/TestSceneBeatSyncedContainer.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 6b56f339d8..e5bcc08924 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -65,19 +65,16 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep($"set mistimed to {(allowMistimed ? "allowed" : "disallowed")}", () => beatContainer.AllowMistimedEventFiring = allowMistimed); - AddStep("bind event", () => + AddStep("Set time before zero", () => { beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => { lastActuationTime = gameplayClockContainer.CurrentTime; lastTimingPoint = timingControlPoint; lastBeatIndex = i; + beatContainer.NewBeat = null; }; - }); - AddStep("Set time before zero", () => - { - lastBeatIndex = null; gameplayClockContainer.Seek(-1000); }); @@ -99,19 +96,14 @@ namespace osu.Game.Tests.Visual.UserInterface int? lastBeatIndex = null; double? lastBpm = null; - AddStep("bind event", () => + AddStep("Set time before zero", () => { beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => { lastBeatIndex = i; lastBpm = timingControlPoint.BPM; }; - }); - AddStep("Set time before zero", () => - { - lastBeatIndex = null; - lastBpm = null; gameplayClockContainer.Seek(-1000); }); From 87d3bd4b9389a3582f15ccfbf054dd8b3fe8e01a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 14:35:43 +0900 Subject: [PATCH 1544/1621] Fix time until next beat potentially being exactly zero at point of trigger --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 78ba716cca..6e4901ab1a 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -151,7 +151,7 @@ namespace osu.Game.Graphics.Containers beatIndex--; TimeUntilNextBeat = (timingPoint.Time - currentTrackTime) % beatLength; - if (TimeUntilNextBeat < 0) + if (TimeUntilNextBeat <= 0) TimeUntilNextBeat += beatLength; TimeSinceLastBeat = beatLength - TimeUntilNextBeat; From d609839ff68e27e1e76d12d3f3cec601d8d68ce6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 14:56:10 +0900 Subject: [PATCH 1545/1621] Fix test not working due to popover container being too global --- .../Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs | 5 ++++- osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index 3ba0f4969a..27fc1dcd51 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs @@ -3,6 +3,9 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Framework.Testing; @@ -60,7 +63,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("attempt join room", () => InputManager.Key(Key.Enter)); - AddUntilStep("password prompt appeared", () => (passwordEntryPopover = loungeScreen.ChildrenOfType().FirstOrDefault()) != null); + AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password"); AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().Click()); diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index 01dd7a25c8..5d2309b88c 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing.Input; using osu.Game.Graphics.Cursor; @@ -35,8 +36,9 @@ namespace osu.Game.Tests.Visual MenuCursorContainer cursorContainer; CompositeDrawable mainContent = - (cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }) - .WithChild(content = new OsuTooltipContainer(cursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }); + new PopoverContainer { RelativeSizeAxes = Axes.Both } + .WithChild(cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }) + .WithChild(content = new OsuTooltipContainer(cursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }); if (CreateNestedActionContainer) { From c966cb053024e180838a8538ec6d46d0f9eb09bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 15:04:18 +0900 Subject: [PATCH 1546/1621] Fix dependency lookup failing due to location of `PopoverContainer` --- .../Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 84c20a6acc..951b00376e 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -243,7 +243,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Alpha = 0; } - public Popover GetPopover() => new PasswordEntryPopover(Room); + public Popover GetPopover() => new PasswordEntryPopover(Room) { JoinRequested = lounge.Join }; public MenuItem[] ContextMenuItems => new MenuItem[] { @@ -350,8 +350,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { private readonly Room room; - [Resolved(canBeNull: true)] - private LoungeSubScreen lounge { get; set; } + public Action JoinRequested; public PasswordEntryPopover(Room room) { @@ -379,7 +378,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { Width = 80, Text = "Join Room", - Action = () => lounge?.Join(room, passwordTextbox.Text) + Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text) } } }; From 567a94a28bd90a5082fda0c43a54a38cc1fdbbda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 15:35:08 +0900 Subject: [PATCH 1547/1621] Remove unused using statements --- .../Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index 27fc1dcd51..bde9ea89ed 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs @@ -3,9 +3,6 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Framework.Testing; From 64cf9b702ededf6e54e4908df54630baec3130c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 17:26:11 +0900 Subject: [PATCH 1548/1621] Fix incorrec nesting of manual input manager test containers --- .../Tests/Visual/OsuManualInputManagerTestScene.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index 5d2309b88c..c5e2e67eaf 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -35,10 +35,16 @@ namespace osu.Game.Tests.Visual { MenuCursorContainer cursorContainer; - CompositeDrawable mainContent = - new PopoverContainer { RelativeSizeAxes = Axes.Both } - .WithChild(cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }) - .WithChild(content = new OsuTooltipContainer(cursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }); + CompositeDrawable mainContent = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Child = cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both, } + }; + + cursorContainer.Child = content = new OsuTooltipContainer(cursorContainer.Cursor) + { + RelativeSizeAxes = Axes.Both + }; if (CreateNestedActionContainer) { From 5b694ad28b1610b24a0d9303d3c39f18e0d14e4b Mon Sep 17 00:00:00 2001 From: huoyaoyuan Date: Sat, 17 Jul 2021 19:37:32 +0800 Subject: [PATCH 1549/1621] Replace string with LocalisableString --- osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs index 443b3dcf01..68faadeaf1 100644 --- a/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs +++ b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs @@ -4,15 +4,16 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays { - public abstract class BreadcrumbControlOverlayHeader : TabControlOverlayHeader + public abstract class BreadcrumbControlOverlayHeader : TabControlOverlayHeader { - protected override OsuTabControl CreateTabControl() => new OverlayHeaderBreadcrumbControl(); + protected override OsuTabControl CreateTabControl() => new OverlayHeaderBreadcrumbControl(); - public class OverlayHeaderBreadcrumbControl : BreadcrumbControl + public class OverlayHeaderBreadcrumbControl : BreadcrumbControl { public OverlayHeaderBreadcrumbControl() { @@ -26,7 +27,7 @@ namespace osu.Game.Overlays AccentColour = colourProvider.Light2; } - protected override TabItem CreateTabItem(string value) => new ControlTabItem(value) + protected override TabItem CreateTabItem(LocalisableString value) => new ControlTabItem(value) { AccentColour = AccentColour, }; @@ -35,7 +36,7 @@ namespace osu.Game.Overlays { protected override float ChevronSize => 8; - public ControlTabItem(string value) + public ControlTabItem(LocalisableString value) : base(value) { RelativeSizeAxes = Axes.Y; From 22ff40fdd5f1362c511f3f2f31be0a1fe0e1c3b5 Mon Sep 17 00:00:00 2001 From: kj415j45 Date: Sat, 17 Jul 2021 19:40:25 +0800 Subject: [PATCH 1550/1621] Fix broken WikiHeader --- osu.Game/Overlays/Wiki/WikiHeader.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiHeader.cs b/osu.Game/Overlays/Wiki/WikiHeader.cs index 6b8cba48b4..2e15fbb566 100644 --- a/osu.Game/Overlays/Wiki/WikiHeader.cs +++ b/osu.Game/Overlays/Wiki/WikiHeader.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Wiki @@ -34,7 +35,7 @@ namespace osu.Game.Overlays.Wiki return; TabControl.Clear(); - Current.Value = null; + Current.Value = string.Empty; TabControl.AddItem(index_page_string); @@ -51,7 +52,7 @@ namespace osu.Game.Overlays.Wiki Current.Value = e.NewValue.Title; } - private void onCurrentChange(ValueChangedEvent e) + private void onCurrentChange(ValueChangedEvent e) { if (e.NewValue == TabControl.Items.LastOrDefault()) return; From e3c10e39945fe7612a467a41ebca735cbd7bbc7a Mon Sep 17 00:00:00 2001 From: huoyaoyuan Date: Sat, 17 Jul 2021 19:40:32 +0800 Subject: [PATCH 1551/1621] Add missing LocalisableString --- osu.Game/Graphics/UserInterface/OsuTabControl.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index c447d7f609..f97880c7ca 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -14,6 +14,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.Framework.Utils; using osu.Game.Graphics.Sprites; @@ -160,7 +161,12 @@ 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)?.GetLocalisableDescription() ?? value.ToString(), + Text = value switch{ + IHasDescription desc => desc?.Description, + Enum e => e.GetLocalisableDescription(), + LocalisableString l => l, + var other => other.ToString() + }, Font = OsuFont.GetFont(size: 14) }, Bar = new Box From 7859d02c5b4e0d65eef8b4ba9639b03c8a45af43 Mon Sep 17 00:00:00 2001 From: bdach Date: Sat, 17 Jul 2021 20:33:26 +0800 Subject: [PATCH 1552/1621] Allow null for breadcrumb control --- osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs | 10 +++++----- osu.Game/Overlays/Wiki/WikiHeader.cs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs index 68faadeaf1..0d383c374f 100644 --- a/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs +++ b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs @@ -9,11 +9,11 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays { - public abstract class BreadcrumbControlOverlayHeader : TabControlOverlayHeader + public abstract class BreadcrumbControlOverlayHeader : TabControlOverlayHeader { - protected override OsuTabControl CreateTabControl() => new OverlayHeaderBreadcrumbControl(); + protected override OsuTabControl CreateTabControl() => new OverlayHeaderBreadcrumbControl(); - public class OverlayHeaderBreadcrumbControl : BreadcrumbControl + public class OverlayHeaderBreadcrumbControl : BreadcrumbControl { public OverlayHeaderBreadcrumbControl() { @@ -27,7 +27,7 @@ namespace osu.Game.Overlays AccentColour = colourProvider.Light2; } - protected override TabItem CreateTabItem(LocalisableString value) => new ControlTabItem(value) + protected override TabItem CreateTabItem(LocalisableString? value) => new ControlTabItem(value) { AccentColour = AccentColour, }; @@ -36,7 +36,7 @@ namespace osu.Game.Overlays { protected override float ChevronSize => 8; - public ControlTabItem(LocalisableString value) + public ControlTabItem(LocalisableString? value) : base(value) { RelativeSizeAxes = Axes.Y; diff --git a/osu.Game/Overlays/Wiki/WikiHeader.cs b/osu.Game/Overlays/Wiki/WikiHeader.cs index 2e15fbb566..fb87486b4e 100644 --- a/osu.Game/Overlays/Wiki/WikiHeader.cs +++ b/osu.Game/Overlays/Wiki/WikiHeader.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Wiki return; TabControl.Clear(); - Current.Value = string.Empty; + Current.Value = null; TabControl.AddItem(index_page_string); @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Wiki Current.Value = e.NewValue.Title; } - private void onCurrentChange(ValueChangedEvent e) + private void onCurrentChange(ValueChangedEvent e) { if (e.NewValue == TabControl.Items.LastOrDefault()) return; From 5b4a1ef70a3294597979d5185919b51ac7a1f28d Mon Sep 17 00:00:00 2001 From: kj415j45 Date: Sat, 17 Jul 2021 20:40:59 +0800 Subject: [PATCH 1553/1621] Update test to match Breadcrumb change --- osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs index 863fa48ddf..e7e6030c66 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Wiki; @@ -96,7 +97,7 @@ namespace osu.Game.Tests.Visual.Online private class TestHeader : WikiHeader { - public IReadOnlyList TabControlItems => TabControl.Items; + public IReadOnlyList TabControlItems => TabControl.Items; } } } From e6b8307b8e637479713accbc4a797a6a5818a7db Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 14:46:14 +0200 Subject: [PATCH 1554/1621] Localise `ProfileHeader` --- osu.Game/Overlays/Profile/ProfileHeader.cs | 33 +++++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index c947ef0781..32bca68f0e 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -7,12 +7,14 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Overlays.Profile.Header; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; namespace osu.Game.Overlays.Profile { - public class ProfileHeader : TabControlOverlayHeader + public class ProfileHeader : TabControlOverlayHeader { private UserCoverBackground coverContainer; @@ -27,8 +29,6 @@ namespace osu.Game.Overlays.Profile User.ValueChanged += e => updateDisplay(e.NewValue); - TabControl.AddItem("info"); - TabControl.AddItem("modding"); centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true); } @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Profile { public ProfileHeaderTitle() { - Title = "player info"; + Title = PageTitleStrings.MainUsersControllerDefault; IconTexture = "Icons/Hexacons/profile"; } } @@ -106,4 +106,29 @@ namespace osu.Game.Overlays.Profile protected override double LoadDelay => 0; } } + + [LocalisableEnum(typeof(ProfileHeaderTabEnumLocalisationMapper))] + public enum ProfileHeaderTab + { + Info, + Modding, + } + + public class ProfileHeaderTabEnumLocalisationMapper : EnumLocalisationMapper + { + public override LocalisableString Map(ProfileHeaderTab value) + { + switch (value) + { + case ProfileHeaderTab.Info: + return LayoutStrings.HeaderUsersShow; + + case ProfileHeaderTab.Modding: + return LayoutStrings.HeaderUsersModding; + + default: + return string.Empty; + } + } + } } From d9c7ea202672298d2ff31678218eae955d3f9271 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 14:57:05 +0200 Subject: [PATCH 1555/1621] Localise profile section titles. --- osu.Game/Overlays/Profile/ProfileSection.cs | 3 ++- osu.Game/Overlays/Profile/Sections/AboutSection.cs | 5 ++++- osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs | 4 +++- osu.Game/Overlays/Profile/Sections/HistoricalSection.cs | 4 +++- osu.Game/Overlays/Profile/Sections/KudosuSection.cs | 4 +++- osu.Game/Overlays/Profile/Sections/MedalsSection.cs | 5 ++++- osu.Game/Overlays/Profile/Sections/RanksSection.cs | 4 +++- osu.Game/Overlays/Profile/Sections/RecentSection.cs | 4 +++- 8 files changed, 25 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index 21f7921da6..1a5f562fff 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; @@ -17,7 +18,7 @@ namespace osu.Game.Overlays.Profile { public abstract class ProfileSection : Container { - public abstract string Title { get; } + public abstract LocalisableString Title { get; } public abstract string Identifier { get; } diff --git a/osu.Game/Overlays/Profile/Sections/AboutSection.cs b/osu.Game/Overlays/Profile/Sections/AboutSection.cs index 1bc01cfd9e..c224d2b1be 100644 --- a/osu.Game/Overlays/Profile/Sections/AboutSection.cs +++ b/osu.Game/Overlays/Profile/Sections/AboutSection.cs @@ -1,11 +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 osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + namespace osu.Game.Overlays.Profile.Sections { public class AboutSection : ProfileSection { - public override string Title => "me!"; + public override LocalisableString Title => UsersStrings.ShowExtraMeTitle; public override string Identifier => "me"; } diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs index c283de42f3..67aaf7d41e 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs @@ -1,14 +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 osu.Framework.Localisation; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile.Sections.Beatmaps; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Sections { public class BeatmapsSection : ProfileSection { - public override string Title => "Beatmaps"; + public override LocalisableString Title => UsersStrings.ShowExtraBeatmapsTitle; public override string Identifier => "beatmaps"; diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index 4fbb7fc7d7..09ca492aa9 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -2,15 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile.Sections.Historical; using osu.Game.Overlays.Profile.Sections.Ranks; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Sections { public class HistoricalSection : ProfileSection { - public override string Title => "Historical"; + public override LocalisableString Title => UsersStrings.ShowExtraHistoricalTitle; public override string Identifier => "historical"; diff --git a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs index a9e9952257..ffa7ef4eaf 100644 --- a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs +++ b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs @@ -3,12 +3,14 @@ using osu.Framework.Graphics; using osu.Game.Overlays.Profile.Sections.Kudosu; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Sections { public class KudosuSection : ProfileSection { - public override string Title => "Kudosu!"; + public override LocalisableString Title => UsersStrings.ShowExtraKudosuTitle; public override string Identifier => "kudosu"; diff --git a/osu.Game/Overlays/Profile/Sections/MedalsSection.cs b/osu.Game/Overlays/Profile/Sections/MedalsSection.cs index 575a2f2c19..3512333e27 100644 --- a/osu.Game/Overlays/Profile/Sections/MedalsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/MedalsSection.cs @@ -1,11 +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 osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + namespace osu.Game.Overlays.Profile.Sections { public class MedalsSection : ProfileSection { - public override string Title => "Medals"; + public override LocalisableString Title => UsersStrings.ShowExtraMedalsTitle; public override string Identifier => "medals"; } diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 33f7c2f71a..a7931c8675 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -3,12 +3,14 @@ using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Online.API.Requests; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Sections { public class RanksSection : ProfileSection { - public override string Title => "Ranks"; + public override LocalisableString Title => UsersStrings.ShowExtraTopRanksTitle; public override string Identifier => "top_ranks"; diff --git a/osu.Game/Overlays/Profile/Sections/RecentSection.cs b/osu.Game/Overlays/Profile/Sections/RecentSection.cs index 1e6cfcc9fd..7a6536c3af 100644 --- a/osu.Game/Overlays/Profile/Sections/RecentSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RecentSection.cs @@ -1,13 +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 osu.Framework.Localisation; using osu.Game.Overlays.Profile.Sections.Recent; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Sections { public class RecentSection : ProfileSection { - public override string Title => "Recent"; + public override LocalisableString Title => UsersStrings.ShowExtraRecentActivityTitle; public override string Identifier => "recent_activity"; From ca1080dfb5d3cfa28319b69785c55e8c2278781c Mon Sep 17 00:00:00 2001 From: kj415j45 Date: Sat, 17 Jul 2021 21:16:57 +0800 Subject: [PATCH 1556/1621] use switch statement Co-authored-by: bdach --- .../Graphics/UserInterface/OsuTabControl.cs | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index f97880c7ca..21e7a68e99 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -154,6 +154,26 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; + LocalisableString text; + switch (value) + { + case IHasDescription hasDescription: + text = hasDescription.GetDescription(); + break; + + case Enum e: + text = e.GetLocalisableDescription(); + break; + + case LocalisableString l: + text = l; + break; + + default: + text = value.ToString(); + break; + }; + Children = new Drawable[] { Text = new OsuSpriteText @@ -161,12 +181,7 @@ namespace osu.Game.Graphics.UserInterface Margin = new MarginPadding { Top = 5, Bottom = 5 }, Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, - Text = value switch{ - IHasDescription desc => desc?.Description, - Enum e => e.GetLocalisableDescription(), - LocalisableString l => l, - var other => other.ToString() - }, + Text = text, Font = OsuFont.GetFont(size: 14) }, Bar = new Box From 4d276b114bbacdd9df278aeaa23ef59a956525ff Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 15:18:45 +0200 Subject: [PATCH 1557/1621] Localise profile header. --- .../Overlays/Profile/Header/Components/ExpandDetailsButton.cs | 3 ++- osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs | 3 ++- .../Overlays/Profile/Header/Components/LevelProgressBar.cs | 3 ++- .../Profile/Header/Components/MappingSubscribersButton.cs | 3 ++- .../Overlays/Profile/Header/Components/MessageUserButton.cs | 3 ++- .../Profile/Header/Components/OverlinedInfoContainer.cs | 3 ++- .../Profile/Header/Components/OverlinedTotalPlayTime.cs | 3 ++- .../Overlays/Profile/Header/Components/PreviousUsernames.cs | 3 ++- 8 files changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs index 16b443875e..b4a5d5e31b 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; +using osu.Game.Resources.Localisation.Web; using osuTK; namespace osu.Game.Overlays.Profile.Header.Components @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly BindableBool DetailsVisible = new BindableBool(); - public override LocalisableString TooltipText => DetailsVisible.Value ? "collapse" : "expand"; + public override LocalisableString TooltipText => DetailsVisible.Value ? CommonStrings.ButtonsCollapse : CommonStrings.ButtonsExpand; private SpriteIcon icon; private Sample sampleOpen; diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index db94762efd..8f66120055 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -13,7 +14,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override LocalisableString TooltipText => "followers"; + public override LocalisableString TooltipText => FriendsStrings.ButtonsDisabled; protected override IconUsage Icon => FontAwesome.Solid.User; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs index 528b05a80a..ed89d78a10 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -10,6 +10,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; using osuTK.Graphics; @@ -26,7 +27,7 @@ namespace osu.Game.Overlays.Profile.Header.Components public LevelProgressBar() { - TooltipText = "progress to next level"; + TooltipText = UsersStrings.ShowStatsLevelProgress; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs index ae3d024fbf..5cdf3a5ef9 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -13,7 +14,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override LocalisableString TooltipText => "mapping subscribers"; + public override LocalisableString TooltipText => FollowsStrings.MappingFollowers; protected override IconUsage Icon => FontAwesome.Solid.Bell; diff --git a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs index 4c2cc768ce..07f1f1c3ed 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Online.API; using osu.Game.Online.Chat; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; using osuTK; @@ -17,7 +18,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override LocalisableString TooltipText => "send message"; + public override LocalisableString TooltipText => UsersStrings.CardSendMessage; [Resolved(CanBeNull = true)] private ChannelManager channelManager { get; set; } diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs index 9f56a34aa6..8f1bbc4097 100644 --- a/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK.Graphics; @@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private readonly OsuSpriteText title; private readonly OsuSpriteText content; - public string Title + public LocalisableString Title { set => title.Text = value; } diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs index aa7cb8636a..1a40944632 100644 --- a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -31,7 +32,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { InternalChild = info = new OverlinedInfoContainer { - Title = "Total Play Time", + Title = UsersStrings.ShowStatsPlayTime, LineColour = colourProvider.Highlight1, }; diff --git a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs index 3cdf110090..14eeb4e5f0 100644 --- a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs +++ b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; using osuTK; @@ -68,7 +69,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Text = @"formerly known as", + Text = UsersStrings.ShowPreviousUsernames, Font = OsuFont.GetFont(size: 10, italics: true) } }, From 306a34a80280dc608dff653666c2e7f5a2caaddf Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 15:21:30 +0200 Subject: [PATCH 1558/1621] Localise level badge tooltip. --- osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs index a0b8ef0f11..1deed1a748 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -19,13 +20,13 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public LocalisableString TooltipText { get; } + public LocalisableString TooltipText { get; private set; } private OsuSpriteText levelText; public LevelBadge() { - TooltipText = "level"; + TooltipText = UsersStrings.ShowStatsLevel("0"); } [BackgroundDependencyLoader] @@ -53,6 +54,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private void updateLevel(User user) { levelText.Text = user?.Statistics?.Level.Current.ToString() ?? "0"; + TooltipText = UsersStrings.ShowStatsLevel(user?.Statistics?.Level.Current.ToString()); } } } From a0c6945f8fd320e3d23ab8b07706dc66b01fcaf6 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 15:25:45 +0200 Subject: [PATCH 1559/1621] Localise user graph. --- osu.Game/Overlays/Profile/Header/Components/RankGraph.cs | 5 +++-- osu.Game/Overlays/Profile/UserGraph.cs | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index ad91e491ef..6bf356c0ff 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -27,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = "No recent plays", + Text = UsersStrings.ShowExtraUnranked, Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular) }); } @@ -74,7 +75,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private class RankGraphTooltip : UserGraphTooltip { public RankGraphTooltip() - : base("Global Ranking") + : base(UsersStrings.ShowRankGlobalSimple) { } diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index cdfd722d68..b7a08b6c5e 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -211,7 +212,7 @@ namespace osu.Game.Overlays.Profile protected readonly OsuSpriteText Counter, BottomText; private readonly Box background; - protected UserGraphTooltip(string tooltipCounterName) + protected UserGraphTooltip(LocalisableString tooltipCounterName) { AutoSizeAxes = Axes.Both; Masking = true; From 2cfec1dc326af464ccb324a3092ac54673b0dad2 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 15:26:18 +0200 Subject: [PATCH 1560/1621] Localise osu!supporter badge. --- osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index 9a43997030..77f0378762 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Graphics; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Header.Components { @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private readonly FillFlowContainer iconContainer; private readonly CircularContainer content; - public LocalisableString TooltipText => "osu!supporter"; + public LocalisableString TooltipText => UsersStrings.ShowIsSupporter; public int SupportLevel { From c6a27e4baaf84cf9e07df687bbd83b6a90c5bb36 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 15:27:40 +0200 Subject: [PATCH 1561/1621] Localise `CentreHeaderContainer`. --- osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 62ebee7677..4195b0b2f1 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; using osuTK; @@ -119,12 +120,12 @@ namespace osu.Game.Overlays.Profile.Header { hiddenDetailGlobal = new OverlinedInfoContainer { - Title = "Global Ranking", + Title = UsersStrings.ShowRankGlobalSimple, LineColour = colourProvider.Highlight1 }, hiddenDetailCountry = new OverlinedInfoContainer { - Title = "Country Ranking", + Title = UsersStrings.ShowRankCountrySimple, LineColour = colourProvider.Highlight1 }, } From 213e3c0716e8706565489a4cd3896e08ca0a5383 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 15:29:34 +0200 Subject: [PATCH 1562/1621] Localise `DetailHeaderContainer` --- osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 574aef02fd..6214e504b0 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Leaderboards; using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; using osu.Game.Users; using osuTK; @@ -100,7 +101,7 @@ namespace osu.Game.Overlays.Profile.Header }, medalInfo = new OverlinedInfoContainer { - Title = "Medals", + Title = UsersStrings.ShowStatsMedals, LineColour = colours.GreenLight, }, ppInfo = new OverlinedInfoContainer @@ -151,12 +152,12 @@ namespace osu.Game.Overlays.Profile.Header { detailGlobalRank = new OverlinedInfoContainer(true, 110) { - Title = "Global Ranking", + Title = UsersStrings.ShowRankGlobalSimple, LineColour = colourProvider.Highlight1, }, detailCountryRank = new OverlinedInfoContainer(false, 110) { - Title = "Country Ranking", + Title = UsersStrings.ShowRankCountrySimple, LineColour = colourProvider.Highlight1, }, } From c6bc95767dc5e2ac3e24596a6d99fd06e3d53ed1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jul 2021 22:31:47 +0900 Subject: [PATCH 1563/1621] Simplify popover hide logic and add test coverage --- .../TestSceneMultiplayerLoungeSubScreen.cs | 12 +++++++++++ .../Lounge/Components/RoomsContainer.cs | 7 ------- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 20 ++++++++++++------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index bde9ea89ed..de46d9e25a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs @@ -52,6 +52,18 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("room join password correct", () => lastJoinedPassword == null); } + [Test] + public void TestPopoverHidesOnLeavingScreen() + { + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + AddStep("select room", () => InputManager.Key(Key.Down)); + AddStep("attempt join room", () => InputManager.Key(Key.Enter)); + + AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType().Any()); + AddStep("exit screen", () => Stack.Exit()); + AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType().Any()); + } + [Test] public void TestJoinRoomWithPassword() { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 0910ff1a7a..07e412ee75 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -7,7 +7,6 @@ using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -239,11 +238,5 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components if (roomManager != null) roomManager.RoomsUpdated -= updateSorting; } - - public void HideAnyPopovers() - { - // must be called on a child of the PopoverContainer due to parent traversal not considering self. - roomFlow.HidePopover(); - } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index dd6106b868..f43109c4fa 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -6,6 +6,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -151,24 +152,29 @@ namespace osu.Game.Screens.OnlinePlay.Lounge onReturning(); } - private void onReturning() - { - filter.HoldFocus = true; - } - public override bool OnExiting(IScreen next) { - filter.HoldFocus = false; + onLeaving(); return base.OnExiting(next); } public override void OnSuspending(IScreen next) { + onLeaving(); base.OnSuspending(next); + } + + private void onReturning() + { + filter.HoldFocus = true; + } + + private void onLeaving() + { filter.HoldFocus = false; // ensure any password prompt is dismissed. - roomsContainer.HideAnyPopovers(); + this.HidePopover(); } public void Join(Room room, string password) From d36842aa15a47a5dd252802970ed9e465fb4552a Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 15:32:28 +0200 Subject: [PATCH 1564/1621] Localise `TopHeaderContainer` --- .../Profile/Header/TopHeaderContainer.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index d751424367..b64dba62e3 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -7,11 +7,13 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; using osu.Game.Users.Drawables; using osuTK; @@ -179,19 +181,19 @@ namespace osu.Game.Overlays.Profile.Header if (user?.Statistics != null) { - userStats.Add(new UserStatsLine("Ranked Score", user.Statistics.RankedScore.ToString("#,##0"))); - userStats.Add(new UserStatsLine("Hit Accuracy", user.Statistics.DisplayAccuracy)); - userStats.Add(new UserStatsLine("Play Count", user.Statistics.PlayCount.ToString("#,##0"))); - userStats.Add(new UserStatsLine("Total Score", user.Statistics.TotalScore.ToString("#,##0"))); - userStats.Add(new UserStatsLine("Total Hits", user.Statistics.TotalHits.ToString("#,##0"))); - userStats.Add(new UserStatsLine("Maximum Combo", user.Statistics.MaxCombo.ToString("#,##0"))); - userStats.Add(new UserStatsLine("Replays Watched by Others", user.Statistics.ReplaysWatched.ToString("#,##0"))); + userStats.Add(new UserStatsLine(UsersStrings.ShowStatsRankedScore, user.Statistics.RankedScore.ToString("#,##0"))); + userStats.Add(new UserStatsLine(UsersStrings.ShowStatsHitAccuracy, user.Statistics.DisplayAccuracy)); + userStats.Add(new UserStatsLine(UsersStrings.ShowStatsPlayCount, user.Statistics.PlayCount.ToString("#,##0"))); + userStats.Add(new UserStatsLine(UsersStrings.ShowStatsTotalScore, user.Statistics.TotalScore.ToString("#,##0"))); + userStats.Add(new UserStatsLine(UsersStrings.ShowStatsTotalHits, user.Statistics.TotalHits.ToString("#,##0"))); + userStats.Add(new UserStatsLine(UsersStrings.ShowStatsMaximumCombo, user.Statistics.MaxCombo.ToString("#,##0"))); + userStats.Add(new UserStatsLine(UsersStrings.ShowStatsReplaysWatchedByOthers, user.Statistics.ReplaysWatched.ToString("#,##0"))); } } private class UserStatsLine : Container { - public UserStatsLine(string left, string right) + public UserStatsLine(LocalisableString left, string right) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; From f4eeb9139ed3784b9ada9f7b13cc307b839f84ab Mon Sep 17 00:00:00 2001 From: kj415j45 Date: Sat, 17 Jul 2021 21:37:58 +0800 Subject: [PATCH 1565/1621] Correct code style --- osu.Game/Graphics/UserInterface/OsuTabControl.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 21e7a68e99..3572ea5c31 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -155,6 +155,7 @@ namespace osu.Game.Graphics.UserInterface RelativeSizeAxes = Axes.Y; LocalisableString text; + switch (value) { case IHasDescription hasDescription: @@ -172,7 +173,7 @@ namespace osu.Game.Graphics.UserInterface default: text = value.ToString(); break; - }; + } Children = new Drawable[] { From 5bb45c7f84fb4b30e310a1723fb2e8dfc0753ce1 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 15:45:17 +0200 Subject: [PATCH 1566/1621] Localise beatmap section. --- .../Sections/Beatmaps/PaginatedBeatmapContainer.cs | 5 +++-- osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs | 10 +++++----- .../Profile/Sections/PaginatedProfileSubsection.cs | 9 +++++---- .../Overlays/Profile/Sections/ProfileSubsection.cs | 5 +++-- .../Profile/Sections/ProfileSubsectionHeader.cs | 5 +++-- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index fe9c710bcc..af1ed9d529 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -19,8 +20,8 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps private const float panel_padding = 10f; private readonly BeatmapSetType type; - public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string headerText) - : base(user, headerText) + public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, LocalisableString headerText) + : base(user, headerText, null) { this.type = type; ItemsPerPage = 6; diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs index 67aaf7d41e..b8fbe73c0c 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs @@ -18,11 +18,11 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { - new PaginatedBeatmapContainer(BeatmapSetType.Favourite, User, "Favourite Beatmaps"), - new PaginatedBeatmapContainer(BeatmapSetType.RankedAndApproved, User, "Ranked & Approved Beatmaps"), - new PaginatedBeatmapContainer(BeatmapSetType.Loved, User, "Loved Beatmaps"), - new PaginatedBeatmapContainer(BeatmapSetType.Unranked, User, "Pending Beatmaps"), - new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, "Graveyarded Beatmaps") + new PaginatedBeatmapContainer(BeatmapSetType.Favourite, User, UsersStrings.ShowExtraBeatmapsFavouriteTitle), + new PaginatedBeatmapContainer(BeatmapSetType.RankedAndApproved, User, UsersStrings.ShowExtraBeatmapsRankedTitle), + new PaginatedBeatmapContainer(BeatmapSetType.Loved, User, UsersStrings.ShowExtraBeatmapsLovedTitle), + new PaginatedBeatmapContainer(BeatmapSetType.Unranked, User, UsersStrings.ShowExtraBeatmapsPendingTitle), + new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, UsersStrings.ShowExtraBeatmapsGraveyardTitle) }; } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index e237b43b2e..25c72a24da 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; +using osu.Framework.Localisation; namespace osu.Game.Overlays.Profile.Sections { @@ -36,9 +37,9 @@ namespace osu.Game.Overlays.Profile.Sections private ShowMoreButton moreButton; private OsuSpriteText missing; - private readonly string missingText; + private readonly LocalisableString? missingText; - protected PaginatedProfileSubsection(Bindable user, string headerText = "", string missingText = "") + protected PaginatedProfileSubsection(Bindable user, LocalisableString headerText, LocalisableString? missingText) : base(user, headerText, CounterVisibilityState.AlwaysVisible) { this.missingText = missingText; @@ -68,7 +69,7 @@ namespace osu.Game.Overlays.Profile.Sections missing = new OsuSpriteText { Font = OsuFont.GetFont(size: 15), - Text = missingText, + Text = missingText ?? string.Empty, Alpha = 0, } } @@ -114,7 +115,7 @@ namespace osu.Game.Overlays.Profile.Sections moreButton.Hide(); moreButton.IsLoading = false; - if (!string.IsNullOrEmpty(missingText)) + if (missingText.HasValue) missing.Show(); return; diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs index 3e331f85e9..a9d2448abf 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Users; using JetBrains.Annotations; +using osu.Framework.Localisation; namespace osu.Game.Overlays.Profile.Sections { @@ -14,12 +15,12 @@ namespace osu.Game.Overlays.Profile.Sections { protected readonly Bindable User = new Bindable(); - private readonly string headerText; + private readonly LocalisableString headerText; private readonly CounterVisibilityState counterVisibilityState; private ProfileSubsectionHeader header; - protected ProfileSubsection(Bindable user, string headerText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + protected ProfileSubsection(Bindable user, LocalisableString headerText, CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) { this.headerText = headerText; this.counterVisibilityState = counterVisibilityState; diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs index 5858cebe89..408cb00770 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osuTK; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; +using osu.Framework.Localisation; namespace osu.Game.Overlays.Profile.Sections { @@ -24,12 +25,12 @@ namespace osu.Game.Overlays.Profile.Sections set => current.Current = value; } - private readonly string text; + private readonly LocalisableString text; private readonly CounterVisibilityState counterState; private CounterPill counterPill; - public ProfileSubsectionHeader(string text, CounterVisibilityState counterState) + public ProfileSubsectionHeader(LocalisableString text, CounterVisibilityState counterState) { this.text = text; this.counterState = counterState; From d17f6589857a1448bdb445dccb3cb0d916a137ff Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 15:50:56 +0200 Subject: [PATCH 1567/1621] Localise Recent section. --- .../Sections/Recent/PaginatedRecentActivityContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index d7101a8147..04c8d7483b 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -10,13 +10,14 @@ using osu.Game.Online.API; using System.Collections.Generic; using osuTK; using osu.Framework.Allocation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Sections.Recent { public class PaginatedRecentActivityContainer : PaginatedProfileSubsection { public PaginatedRecentActivityContainer(Bindable user) - : base(user, missingText: "This user hasn't done anything notable recently!") + : base(user, string.Empty, EventsStrings.Empty) { ItemsPerPage = 10; } From fbbf8ce5a3950f7a3da6a08ec61c64c4f5a2b1f7 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 15:53:24 +0200 Subject: [PATCH 1568/1621] Localise Ranks section. --- .../Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs | 2 +- .../Overlays/Profile/Sections/PaginatedProfileSubsection.cs | 2 +- .../Profile/Sections/Ranks/PaginatedScoreContainer.cs | 3 ++- osu.Game/Overlays/Profile/Sections/RanksSection.cs | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index af1ed9d529..ec64371a5d 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps private readonly BeatmapSetType type; public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, LocalisableString headerText) - : base(user, headerText, null) + : base(user, headerText) { this.type = type; ItemsPerPage = 6; diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 25c72a24da..4a37c94abe 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Profile.Sections private OsuSpriteText missing; private readonly LocalisableString? missingText; - protected PaginatedProfileSubsection(Bindable user, LocalisableString headerText, LocalisableString? missingText) + protected PaginatedProfileSubsection(Bindable user, LocalisableString headerText, LocalisableString? missingText = null) : base(user, headerText, CounterVisibilityState.AlwaysVisible) { this.missingText = missingText; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 720cd4a3db..7c04b331c2 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -11,6 +11,7 @@ using osu.Game.Online.API.Requests.Responses; using System.Collections.Generic; using osu.Game.Online.API; using osu.Framework.Allocation; +using osu.Framework.Localisation; namespace osu.Game.Overlays.Profile.Sections.Ranks { @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly ScoreType type; - public PaginatedScoreContainer(ScoreType type, Bindable user, string headerText) + public PaginatedScoreContainer(ScoreType type, Bindable user, LocalisableString headerText) : base(user, headerText) { this.type = type; diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index a7931c8675..5e0648dbb1 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -18,8 +18,8 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { - new PaginatedScoreContainer(ScoreType.Best, User, "Best Performance"), - new PaginatedScoreContainer(ScoreType.Firsts, User, "First Place Ranks") + new PaginatedScoreContainer(ScoreType.Best, User, UsersStrings.ShowExtraTopRanksBestTitle), + new PaginatedScoreContainer(ScoreType.Firsts, User, UsersStrings.ShowExtraTopRanksFirstTitle) }; } } From 2f3ed4a4ab080d7758afc6d2786a7d1118b4f736 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 16:13:33 +0200 Subject: [PATCH 1569/1621] Fix `PaginatedProfileSubsection` ctor arguments --- .../Overlays/Profile/Sections/PaginatedProfileSubsection.cs | 2 +- osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 4a37c94abe..d60243cd0a 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Profile.Sections private OsuSpriteText missing; private readonly LocalisableString? missingText; - protected PaginatedProfileSubsection(Bindable user, LocalisableString headerText, LocalisableString? missingText = null) + protected PaginatedProfileSubsection(Bindable user, LocalisableString? headerText = null, LocalisableString? missingText = null) : base(user, headerText, CounterVisibilityState.AlwaysVisible) { this.missingText = missingText; diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs index a9d2448abf..5a17f0d8bb 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -20,9 +20,9 @@ namespace osu.Game.Overlays.Profile.Sections private ProfileSubsectionHeader header; - protected ProfileSubsection(Bindable user, LocalisableString headerText, CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + protected ProfileSubsection(Bindable user, LocalisableString? headerText = null, CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) { - this.headerText = headerText; + this.headerText = headerText ?? string.Empty; this.counterVisibilityState = counterVisibilityState; User.BindTo(user); } @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Profile.Sections { header = new ProfileSubsectionHeader(headerText, counterVisibilityState) { - Alpha = string.IsNullOrEmpty(headerText) ? 0 : 1 + Alpha = string.IsNullOrEmpty(headerText.ToString()) ? 0 : 1 }, CreateContent() }; From 148eb890fff5707f28d586121db754561943d81d Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 16:20:37 +0200 Subject: [PATCH 1570/1621] Localise Historical section. --- .../Sections/Historical/ChartProfileSubsection.cs | 5 +++-- .../Sections/Historical/DrawableMostPlayedBeatmap.cs | 3 ++- .../Historical/PaginatedMostPlayedBeatmapContainer.cs | 3 ++- .../Sections/Historical/PlayHistorySubsection.cs | 6 ++++-- .../Profile/Sections/Historical/ProfileLineChart.cs | 3 ++- .../Profile/Sections/Historical/ReplaysSubsection.cs | 6 ++++-- .../Profile/Sections/Historical/UserHistoryGraph.cs | 11 ++++++----- .../Overlays/Profile/Sections/HistoricalSection.cs | 2 +- 8 files changed, 24 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs index a48036dcbb..986b3d9874 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Game.Users; using static osu.Game.Users.User; @@ -18,9 +19,9 @@ namespace osu.Game.Overlays.Profile.Sections.Historical /// /// Text describing the value being plotted on the graph, which will be displayed as a prefix to the value in the history graph tooltip. /// - protected abstract string GraphCounterName { get; } + protected abstract LocalisableString GraphCounterName { get; } - protected ChartProfileSubsection(Bindable user, string headerText) + protected ChartProfileSubsection(Bindable user, LocalisableString headerText) : base(user, headerText) { } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 6f1869966a..a419bef233 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Sprites; using osuTK; using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Sections.Historical { @@ -143,7 +144,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private class PlayCountText : CompositeDrawable, IHasTooltip { - public LocalisableString TooltipText => "times played"; + public LocalisableString TooltipText => UsersStrings.ShowExtraHistoricalMostPlayedCount; public PlayCountText(int playCount) { diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index eeb14e5e4f..d0979526da 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Sections.Historical @@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical public class PaginatedMostPlayedBeatmapContainer : PaginatedProfileSubsection { public PaginatedMostPlayedBeatmapContainer(Bindable user) - : base(user, "Most Played Beatmaps") + : base(user, UsersStrings.ShowExtraHistoricalMostPlayedTitle) { ItemsPerPage = 5; } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs index dfd29db693..83c005970e 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; using static osu.Game.Users.User; @@ -9,10 +11,10 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public class PlayHistorySubsection : ChartProfileSubsection { - protected override string GraphCounterName => "Plays"; + protected override LocalisableString GraphCounterName => UsersStrings.ShowExtraHistoricalMonthlyPlaycountsCountLabel; public PlayHistorySubsection(Bindable user) - : base(user, "Play History") + : base(user, UsersStrings.ShowExtraHistoricalMonthlyPlaycountsTitle) { } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index eb5deb2802..af39251781 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics; using osu.Framework.Graphics.Shapes; using osuTK; using static osu.Game.Users.User; +using osu.Framework.Localisation; namespace osu.Game.Overlays.Profile.Sections.Historical { @@ -42,7 +43,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private readonly Container rowLinesContainer; private readonly Container columnLinesContainer; - public ProfileLineChart(string graphCounterName) + public ProfileLineChart(LocalisableString graphCounterName) { RelativeSizeAxes = Axes.X; Height = 250; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs index 1c28306f17..76d5f73bd7 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; using static osu.Game.Users.User; @@ -9,10 +11,10 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public class ReplaysSubsection : ChartProfileSubsection { - protected override string GraphCounterName => "Replays Watched"; + protected override LocalisableString GraphCounterName => UsersStrings.ShowExtraHistoricalReplaysWatchedCountsCountLabel; public ReplaysSubsection(Bindable user) - : base(user, "Replays Watched History") + : base(user, UsersStrings.ShowExtraHistoricalReplaysWatchedCountsTitle) { } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index 52831b4243..d626c63fed 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -5,13 +5,14 @@ using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; +using osu.Framework.Localisation; using static osu.Game.Users.User; namespace osu.Game.Overlays.Profile.Sections.Historical { public class UserHistoryGraph : UserGraph { - private readonly string tooltipCounterName; + private readonly LocalisableString tooltipCounterName; [CanBeNull] public UserHistoryCount[] Values @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical set => Data = value?.Select(v => new KeyValuePair(v.Date, v.Count)).ToArray(); } - public UserHistoryGraph(string tooltipCounterName) + public UserHistoryGraph(LocalisableString tooltipCounterName) { this.tooltipCounterName = tooltipCounterName; } @@ -40,9 +41,9 @@ namespace osu.Game.Overlays.Profile.Sections.Historical protected class HistoryGraphTooltip : UserGraphTooltip { - private readonly string tooltipCounterName; + private readonly LocalisableString tooltipCounterName; - public HistoryGraphTooltip(string tooltipCounterName) + public HistoryGraphTooltip(LocalisableString tooltipCounterName) : base(tooltipCounterName) { this.tooltipCounterName = tooltipCounterName; @@ -61,7 +62,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private class TooltipDisplayContent { - public string Name; + public LocalisableString Name; public string Count; public string Date; } diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index 09ca492aa9..cba25c0a8b 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Profile.Sections { new PlayHistorySubsection(User), new PaginatedMostPlayedBeatmapContainer(User), - new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)"), + new PaginatedScoreContainer(ScoreType.Recent, User, UsersStrings.ShowExtraHistoricalRecentPlaysTitle), new ReplaysSubsection(User) }; } From 2545275f7102e0607d81eaced1c00db66efb08c1 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 16:29:09 +0200 Subject: [PATCH 1571/1621] Partly localise Kudosu section. --- osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index cdb24b784c..37de669b3b 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -12,6 +12,8 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; using osu.Framework.Allocation; +using osu.Game.Resources.Localisation.Web; +using osu.Framework.Localisation; namespace osu.Game.Overlays.Profile.Sections.Kudosu { @@ -37,7 +39,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu private class CountTotal : CountSection { public CountTotal() - : base("Total Kudosu Earned") + : base(UsersStrings.ShowExtraKudosuTotal) { DescriptionText.AddText("Based on how much of a contribution the user has made to beatmap moderation. See "); DescriptionText.AddLink("this page", "https://osu.ppy.sh/wiki/Kudosu"); @@ -56,7 +58,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu set => valueText.Text = value.ToString("N0"); } - public CountSection(string header) + public CountSection(LocalisableString header) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; From a7c280508f9480141da9b324edddf613e7c7c022 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 16:40:37 +0200 Subject: [PATCH 1572/1621] Throw instead of silently returning. --- osu.Game/Overlays/Profile/ProfileHeader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 32bca68f0e..083725418e 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.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.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -127,7 +128,7 @@ namespace osu.Game.Overlays.Profile return LayoutStrings.HeaderUsersModding; default: - return string.Empty; + throw new ArgumentOutOfRangeException(nameof(value), value, null); } } } From 77d8f240f8e33d75f2f92caf0c6d7e12b7465463 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 16:41:42 +0200 Subject: [PATCH 1573/1621] Use ctor default values. --- .../Profile/Sections/Recent/PaginatedRecentActivityContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index 04c8d7483b..db2e6bc1e0 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent public class PaginatedRecentActivityContainer : PaginatedProfileSubsection { public PaginatedRecentActivityContainer(Bindable user) - : base(user, string.Empty, EventsStrings.Empty) + : base(user, missingText: EventsStrings.Empty) { ItemsPerPage = 10; } From 28845364a3380980a101f984f38312d792f9f376 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 16:52:35 +0200 Subject: [PATCH 1574/1621] Localise score weighting. --- .../Profile/Sections/Ranks/DrawableProfileWeightedScore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index 3afa79e59e..63305d004c 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; using osuTK; @@ -51,7 +52,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks new OsuSpriteText { Font = OsuFont.GetFont(size: 12), - Text = $@"weighted {weight:0%}" + Text = UsersStrings.ShowExtraTopRanksPpWeight(weight.ToString("0%")) } } }; From 2c26248042e58a6db18531fbbe00980e7fdada8f Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 17 Jul 2021 17:36:49 +0200 Subject: [PATCH 1575/1621] Localise missing text of `PaginatedKudosuHistoryContainer`. --- .../Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs index 008d89d881..76cd7ed722 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs @@ -8,13 +8,14 @@ using osu.Framework.Bindables; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API; using System.Collections.Generic; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Sections.Kudosu { public class PaginatedKudosuHistoryContainer : PaginatedProfileSubsection { public PaginatedKudosuHistoryContainer(Bindable user) - : base(user, missingText: "This user hasn't received any kudosu!") + : base(user, missingText: UsersStrings.ShowExtraKudosuEntryEmpty) { ItemsPerPage = 5; } From 9a2fb8ca6c26daf8cb926f8744cc550496e7e5d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Jul 2021 18:06:45 +0200 Subject: [PATCH 1576/1621] Add test coverage for null mod on seeding screen --- .../Screens/TestSceneSeedingScreen.cs | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs index d414d8e36e..a18e73e38f 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs @@ -1,9 +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.Linq; +using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Ladder.Components; using osu.Game.Tournament.Screens.TeamIntro; namespace osu.Game.Tournament.Tests.Screens @@ -11,16 +15,41 @@ namespace osu.Game.Tournament.Tests.Screens public class TestSceneSeedingScreen : TournamentTestScene { [Cached] - private readonly LadderInfo ladder = new LadderInfo(); - - [BackgroundDependencyLoader] - private void load() + private readonly LadderInfo ladder = new LadderInfo { - Add(new SeedingScreen + Teams = + { + new TournamentTeam + { + FullName = { Value = @"Japan" }, + Acronym = { Value = "JPN" }, + SeedingResults = + { + new SeedingResult + { + // Mod intentionally left blank. + Seed = { Value = 4 } + }, + new SeedingResult + { + Mod = { Value = "DT" }, + Seed = { Value = 8 } + } + } + } + } + }; + + [Test] + public void TestBasic() + { + AddStep("create seeding screen", () => Add(new SeedingScreen { FillMode = FillMode.Fit, FillAspectRatio = 16 / 9f - }); + })); + + AddStep("set team to Japan", () => this.ChildrenOfType().Single().Current.Value = ladder.Teams.Single()); } } } From 714255e6d4059e72e46083328367e1d534726581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Jul 2021 17:53:58 +0200 Subject: [PATCH 1577/1621] Fix seeding screen crashing on seedings with null mod --- .../Screens/TeamIntro/SeedingScreen.cs | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 4f66d89b7f..2f82578586 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -179,44 +179,48 @@ namespace osu.Game.Tournament.Screens.TeamIntro [BackgroundDependencyLoader] private void load(TextureStore textures) { + FillFlowContainer row; + InternalChildren = new Drawable[] { - new FillFlowContainer + row = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(5), - Children = new Drawable[] - { - new Sprite - { - Texture = textures.Get($"mods/{mods.ToLower()}"), - Scale = new Vector2(0.5f) - }, - new Container - { - Size = new Vector2(50, 16), - CornerRadius = 10, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = TournamentGame.ELEMENT_BACKGROUND_COLOUR, - }, - new TournamentSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = seeding.ToString("#,0"), - Colour = TournamentGame.ELEMENT_FOREGROUND_COLOUR - }, - } - }, - } }, }; + + if (!string.IsNullOrEmpty(mods)) + { + row.Add(new Sprite + { + Texture = textures.Get($"mods/{mods.ToLower()}"), + Scale = new Vector2(0.5f) + }); + } + + row.Add(new Container + { + Size = new Vector2(50, 16), + CornerRadius = 10, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = TournamentGame.ELEMENT_BACKGROUND_COLOUR, + }, + new TournamentSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = seeding.ToString("#,0"), + Colour = TournamentGame.ELEMENT_FOREGROUND_COLOUR + }, + } + }); } } } From 49f0c707f6cb4e5676105f88e015966dbc41794c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Jul 2021 22:34:28 +0900 Subject: [PATCH 1578/1621] Move approach circle hiding within `BeginAbsoluteSequence` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 5fa3bc4520..471847dbd6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -147,21 +147,22 @@ namespace osu.Game.Rulesets.Osu.Mods double startTime = circle.HitObject.StartTime; double preempt = circle.HitObject.TimePreempt; - using (drawable.BeginAbsoluteSequence(startTime - preempt)) + using (circle.BeginAbsoluteSequence(startTime - preempt)) { // initial state - drawable.ScaleTo(0.5f) + circle.ScaleTo(0.5f) .FadeColour(OsuColour.Gray(0.5f)); // scale to final size - drawable.ScaleTo(1f, preempt); + circle.ScaleTo(1f, preempt); + + // Remove approach circles + circle.ApproachCircle.Hide(); } - using (drawable.BeginAbsoluteSequence(startTime - controlPointInfo.TimingPointAt(startTime).BeatLength - undim_duration)) - drawable.FadeColour(Color4.White, undim_duration); + using (circle.BeginAbsoluteSequence(startTime - controlPointInfo.TimingPointAt(startTime).BeatLength - undim_duration)) + circle.FadeColour(Color4.White, undim_duration); - // Remove approach circles - circle.ApproachCircle.Hide(); } #endregion From fdebe4b94ac30050334dc6330308c54506a50079 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 18 Jul 2021 22:01:26 +0800 Subject: [PATCH 1579/1621] Code formatting fixes --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 471847dbd6..918b9b1c94 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.Mods { // initial state circle.ScaleTo(0.5f) - .FadeColour(OsuColour.Gray(0.5f)); + .FadeColour(OsuColour.Gray(0.5f)); // scale to final size circle.ScaleTo(1f, preempt); @@ -162,7 +162,6 @@ namespace osu.Game.Rulesets.Osu.Mods using (circle.BeginAbsoluteSequence(startTime - controlPointInfo.TimingPointAt(startTime).BeatLength - undim_duration)) circle.FadeColour(Color4.White, undim_duration); - } #endregion From 9257cd7fad4c3883389d8620daffd78b1e18cee7 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 18 Jul 2021 19:18:06 +0200 Subject: [PATCH 1580/1621] Revert the use of an enum in `ProfileHeader`. --- osu.Game/Overlays/Profile/ProfileHeader.cs | 31 +++------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 083725418e..88c785d4e1 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.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.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -15,7 +14,7 @@ using osu.Game.Users; namespace osu.Game.Overlays.Profile { - public class ProfileHeader : TabControlOverlayHeader + public class ProfileHeader : TabControlOverlayHeader { private UserCoverBackground coverContainer; @@ -30,6 +29,9 @@ namespace osu.Game.Overlays.Profile User.ValueChanged += e => updateDisplay(e.NewValue); + TabControl.AddItem(LayoutStrings.HeaderUsersShow); + TabControl.AddItem(LayoutStrings.HeaderUsersModding); + centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true); } @@ -107,29 +109,4 @@ namespace osu.Game.Overlays.Profile protected override double LoadDelay => 0; } } - - [LocalisableEnum(typeof(ProfileHeaderTabEnumLocalisationMapper))] - public enum ProfileHeaderTab - { - Info, - Modding, - } - - public class ProfileHeaderTabEnumLocalisationMapper : EnumLocalisationMapper - { - public override LocalisableString Map(ProfileHeaderTab value) - { - switch (value) - { - case ProfileHeaderTab.Info: - return LayoutStrings.HeaderUsersShow; - - case ProfileHeaderTab.Modding: - return LayoutStrings.HeaderUsersModding; - - default: - throw new ArgumentOutOfRangeException(nameof(value), value, null); - } - } - } } From 80885301a391e57d5d413337e87b1495da7624f1 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 18 Jul 2021 19:36:34 +0200 Subject: [PATCH 1581/1621] Fix codefactor issues. --- osu.Game/Overlays/Profile/ProfileHeader.cs | 1 - .../Overlays/Profile/Sections/Historical/ProfileLineChart.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 88c785d4e1..815f9fdafc 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -32,7 +32,6 @@ namespace osu.Game.Overlays.Profile TabControl.AddItem(LayoutStrings.HeaderUsersShow); TabControl.AddItem(LayoutStrings.HeaderUsersModding); - centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true); } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index af39251781..e6fd09301e 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -12,8 +12,8 @@ using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Framework.Graphics.Shapes; using osuTK; -using static osu.Game.Users.User; using osu.Framework.Localisation; +using static osu.Game.Users.User; namespace osu.Game.Overlays.Profile.Sections.Historical { From fb5d25405e82013fcc71bd06786dc68d94435662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jul 2021 21:52:16 +0200 Subject: [PATCH 1582/1621] Replace calls to obsoleted `GetOrDefault()` extension --- .../Difficulty/CatchPerformanceCalculator.cs | 11 +++++------ .../Difficulty/ManiaPerformanceCalculator.cs | 13 ++++++------- .../Difficulty/OsuPerformanceCalculator.cs | 9 ++++----- .../Difficulty/TaikoPerformanceCalculator.cs | 9 ++++----- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 11 +++++------ osu.Game/Scoring/ScoreInfo.cs | 7 +++---- osu.Game/Scoring/ScoreManager.cs | 3 +-- 7 files changed, 28 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index fdd6ac0857..439890dac2 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Extensions; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -33,11 +32,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty { mods = Score.Mods; - fruitsHit = Score.Statistics.GetOrDefault(HitResult.Great); - ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit); - tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit); - tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss); - misses = Score.Statistics.GetOrDefault(HitResult.Miss); + fruitsHit = Score.Statistics.GetValueOrDefault(HitResult.Great); + ticksHit = Score.Statistics.GetValueOrDefault(HitResult.LargeTickHit); + tinyTicksHit = Score.Statistics.GetValueOrDefault(HitResult.SmallTickHit); + tinyTicksMissed = Score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss); + misses = Score.Statistics.GetValueOrDefault(HitResult.Miss); // We are heavily relying on aim in catch the beat double value = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0; diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 405ac56e94..b04ff3548f 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Extensions; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -37,12 +36,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty { mods = Score.Mods; scaledScore = Score.TotalScore; - countPerfect = Score.Statistics.GetOrDefault(HitResult.Perfect); - countGreat = Score.Statistics.GetOrDefault(HitResult.Great); - countGood = Score.Statistics.GetOrDefault(HitResult.Good); - countOk = Score.Statistics.GetOrDefault(HitResult.Ok); - countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); - countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); + countPerfect = Score.Statistics.GetValueOrDefault(HitResult.Perfect); + countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great); + countGood = Score.Statistics.GetValueOrDefault(HitResult.Good); + countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok); + countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); + countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); IEnumerable scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 8a3c426381..a76db4abe3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Extensions; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -36,10 +35,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty mods = Score.Mods; accuracy = Score.Accuracy; scoreMaxCombo = Score.MaxCombo; - countGreat = Score.Statistics.GetOrDefault(HitResult.Great); - countOk = Score.Statistics.GetOrDefault(HitResult.Ok); - countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); - countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); + countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great); + countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok); + countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); + countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); // Custom multipliers for NoFail and SpunOut. double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 6117ed1673..90dd733dfd 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Extensions; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -31,10 +30,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty public override double Calculate(Dictionary categoryDifficulty = null) { mods = Score.Mods; - countGreat = Score.Statistics.GetOrDefault(HitResult.Great); - countOk = Score.Statistics.GetOrDefault(HitResult.Ok); - countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); - countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); + countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great); + countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok); + countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); + countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); // Custom multipliers for NoFail and SpunOut. double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index f32f70d4ba..eeb881cd39 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; @@ -146,7 +145,7 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore += result.Judgement.MaxNumericResult; } - scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; + scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1; hitEvents.Add(CreateHitEvent(result)); lastHitObject = result.HitObject; @@ -181,7 +180,7 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } - scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; + scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1; Debug.Assert(hitEvents.Count > 0); lastHitObject = hitEvents[^1].LastHitObject; @@ -272,8 +271,8 @@ namespace osu.Game.Rulesets.Scoring private double calculateComboRatio(int maxCombo) => maxAchievableCombo > 0 ? (double)maxCombo / maxAchievableCombo : 1; private double getBonusScore(Dictionary statistics) - => statistics.GetOrDefault(HitResult.SmallBonus) * Judgement.SMALL_BONUS_SCORE - + statistics.GetOrDefault(HitResult.LargeBonus) * Judgement.LARGE_BONUS_SCORE; + => statistics.GetValueOrDefault(HitResult.SmallBonus) * Judgement.SMALL_BONUS_SCORE + + statistics.GetValueOrDefault(HitResult.LargeBonus) * Judgement.LARGE_BONUS_SCORE; private ScoreRank rankFrom(double acc) { @@ -291,7 +290,7 @@ namespace osu.Game.Rulesets.Scoring return ScoreRank.D; } - public int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result); + public int GetStatistic(HitResult result) => scoreResultCounts.GetValueOrDefault(result); public double GetStandardisedScore() => getScore(ScoringMode.Standardised); diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 4fd1d00fef..1d22dab0af 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -7,7 +7,6 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; @@ -209,13 +208,13 @@ namespace osu.Game.Scoring { foreach (var r in Ruleset.CreateInstance().GetHitResults()) { - int value = Statistics.GetOrDefault(r.result); + int value = Statistics.GetValueOrDefault(r.result); switch (r.result) { case HitResult.SmallTickHit: { - int total = value + Statistics.GetOrDefault(HitResult.SmallTickMiss); + int total = value + Statistics.GetValueOrDefault(HitResult.SmallTickMiss); if (total > 0) yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); @@ -224,7 +223,7 @@ namespace osu.Game.Scoring case HitResult.LargeTickHit: { - int total = value + Statistics.GetOrDefault(HitResult.LargeTickMiss); + int total = value + Statistics.GetValueOrDefault(HitResult.LargeTickMiss); if (total > 0) yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index ebbdc8a109..83bcac01ac 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -210,7 +209,7 @@ namespace osu.Game.Scoring { // This is guaranteed to be a non-legacy score. // The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values. - beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetOrDefault(r)).Sum(); + beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum(); } updateScore(beatmapMaxCombo, accuracy); From e0af5f0469d97a0b4b371cc0b4874d5aeed0294e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 11:38:28 +0900 Subject: [PATCH 1583/1621] Fix osu!(lazer) overwriting osu!(stable) desktop icons by adding back a suffix As discussed in https://github.com/ppy/osu/issues/13864, Squirrel will use the product name before the title, allowing us to use this variable to update the icon while not changing the window display title or naming elsewhere. --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 53a4e5edf5..89b9ffb94b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -6,7 +6,7 @@ A free-to-win rhythm game. Rhythm is just a *click* away! osu! osu! - osu! + osu!(lazer) lazer.ico app.manifest 0.0.0 From d7f997a7f3f221279ee4b1497129655950adb525 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 12:36:13 +0900 Subject: [PATCH 1584/1621] Set score's rank on a failed submission As we don't have a `RankInfo.F`, this is the next best choice. I am also adding a check osu-web side for this - this is just to make sure we aren't sending scores with SS when they are not actually completed. I'm working on a separate PR to ensure this does not get mutated during the player exit process. --- osu.Game/Screens/Play/Player.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 97854ee12f..dc7ac1a48b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -953,7 +953,11 @@ namespace osu.Game.Screens.Play // if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap. if (prepareScoreForDisplayTask == null) + { Score.ScoreInfo.Passed = false; + // potentially should be ScoreRank.F instead? this is the best alternative for now. + Score.ScoreInfo.Rank = ScoreRank.D; + } // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. // To resolve test failures, forcefully end playing synchronously when this screen exits. From 3c028ce05cd619c58488793e9e26548ae07aeff3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 12:38:22 +0900 Subject: [PATCH 1585/1621] Add `IDeepCloneable` interface and update existing `CreateCopy` methods to use it --- CodeAnalysis/BannedSymbols.txt | 1 + osu.Game.Tests/NonVisual/ControlPointInfoTest.cs | 4 ++-- .../Visual/UserInterface/TestSceneModSettings.cs | 4 ++-- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 5 +++-- .../Beatmaps/ControlPoints/ControlPointInfo.cs | 7 ++++--- osu.Game/Online/Rooms/Room.cs | 5 +++-- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- .../Rulesets/Difficulty/DifficultyCalculator.cs | 2 +- osu.Game/Rulesets/Mods/Mod.cs | 4 ++-- osu.Game/Rulesets/Mods/MultiMod.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- .../OnlinePlay/Lounge/Components/DrawableRoom.cs | 2 +- .../Screens/OnlinePlay/OnlinePlaySongSelect.cs | 8 ++++---- .../OnlinePlay/Playlists/PlaylistsSongSelect.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Utils/IDeepCloneable.cs | 16 ++++++++++++++++ 16 files changed, 45 insertions(+), 25 deletions(-) create mode 100644 osu.Game/Utils/IDeepCloneable.cs diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 46c50dbfa2..ea3e25142c 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -3,6 +3,7 @@ M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Us M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable or EqualityComparer.Default instead. M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead. T:System.IComparable;Don't use non-generic IComparable. Use generic version instead. +T:SixLabors.ImageSharp.IDeepCloneable`1;Use osu.Game.Utils.IDeepCloneable instead. M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900) T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods. diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs index b27c257795..240ae4a90c 100644 --- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -248,13 +248,13 @@ namespace osu.Game.Tests.NonVisual } [Test] - public void TestCreateCopyIsDeepClone() + public void TestDeepClone() { var cpi = new ControlPointInfo(); cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); - var cpiCopy = cpi.CreateCopy(); + var cpiCopy = cpi.DeepClone(); cpiCopy.Add(2000, new TimingControlPoint { BeatLength = 500 }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index bda1973354..65db2e9644 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("create mods", () => { original = new OsuModDoubleTime(); - copy = (OsuModDoubleTime)original.CreateCopy(); + copy = (OsuModDoubleTime)original.DeepClone(); }); AddStep("change property", () => original.SpeedChange.Value = 2); @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("create mods", () => { original = new MultiMod(new OsuModDoubleTime()); - copy = (MultiMod)original.CreateCopy(); + copy = (MultiMod)original.DeepClone(); }); AddStep("change property", () => ((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value = 2); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index e8dc623ddb..643c5d9adb 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -3,11 +3,12 @@ using System; using osu.Game.Graphics; +using osu.Game.Utils; using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints { - public abstract class ControlPoint : IComparable + public abstract class ControlPoint : IComparable, IDeepCloneable { /// /// The time at which the control point takes effect. @@ -32,7 +33,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// Create an unbound copy of this control point. /// - public ControlPoint CreateCopy() + public ControlPoint DeepClone() { var copy = (ControlPoint)Activator.CreateInstance(GetType()); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 25d0843a71..2d0fc17a7b 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -10,11 +10,12 @@ using osu.Framework.Bindables; using osu.Framework.Lists; using osu.Framework.Utils; using osu.Game.Screens.Edit; +using osu.Game.Utils; namespace osu.Game.Beatmaps.ControlPoints { [Serializable] - public class ControlPointInfo + public class ControlPointInfo : IDeepCloneable { /// /// All control points grouped by time. @@ -350,12 +351,12 @@ namespace osu.Game.Beatmaps.ControlPoints } } - public ControlPointInfo CreateCopy() + public ControlPointInfo DeepClone() { var controlPointInfo = new ControlPointInfo(); foreach (var point in AllControlPoints) - controlPointInfo.Add(point.Time, point.CreateCopy()); + controlPointInfo.Add(point.Time, point.DeepClone()); return controlPointInfo; } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index b28680ffef..863fefd55c 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -10,10 +10,11 @@ using osu.Game.IO.Serialization.Converters; using osu.Game.Online.Rooms.GameTypes; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Users; +using osu.Game.Utils; namespace osu.Game.Online.Rooms { - public class Room + public class Room : IDeepCloneable { [Cached] [JsonProperty("id")] @@ -120,7 +121,7 @@ namespace osu.Game.Online.Rooms /// Create a copy of this room without online information. /// Should be used to create a local copy of a room for submitting in the future. /// - public Room CreateCopy() + public Room DeepClone() { var copy = new Room(); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 98a79a62c8..793bb79318 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -429,7 +429,7 @@ namespace osu.Game.Overlays.Mods if (!Stacked) modEnumeration = ModUtils.FlattenMods(modEnumeration); - section.Mods = modEnumeration.Select(getValidModOrNull).Where(m => m != null).Select(m => m.CreateCopy()); + section.Mods = modEnumeration.Select(getValidModOrNull).Where(m => m != null).Select(m => m.DeepClone()); } updateSelectedButtons(); diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 3cc69bd85b..224c9178ae 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Difficulty /// A structure describing the difficulty of the beatmap. public DifficultyAttributes Calculate(params Mod[] mods) { - mods = mods.Select(m => m.CreateCopy()).ToArray(); + mods = mods.Select(m => m.DeepClone()).ToArray(); IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 6f00bb6c75..f2fd02c652 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods /// The base class for gameplay modifiers. /// [ExcludeFromDynamicCompile] - public abstract class Mod : IMod, IEquatable, IJsonSerializable + public abstract class Mod : IMod, IEquatable, IJsonSerializable, IDeepCloneable { /// /// The name of this mod. @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Mods /// /// Creates a copy of this initialised to a default state. /// - public virtual Mod CreateCopy() + public virtual Mod DeepClone() { var result = (Mod)Activator.CreateInstance(GetType()); result.CopyFrom(this); diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs index 2107009dbb..1c41c6b8b3 100644 --- a/osu.Game/Rulesets/Mods/MultiMod.cs +++ b/osu.Game/Rulesets/Mods/MultiMod.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods Mods = mods; } - public override Mod CreateCopy() => new MultiMod(Mods.Select(m => m.CreateCopy()).ToArray()); + public override Mod DeepClone() => new MultiMod(Mods.Select(m => m.DeepClone()).ToArray()); public override Type[] IncompatibleMods => Mods.SelectMany(m => m.IncompatibleMods).ToArray(); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 986a4efb28..71dd47b058 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Edit // clone these locally for now to avoid incurring overhead on GetPlayableBeatmap usages. // eventually we will want to improve how/where this is done as there are issues with *not* cloning it in all cases. - playableBeatmap.ControlPointInfo = playableBeatmap.ControlPointInfo.CreateCopy(); + playableBeatmap.ControlPointInfo = playableBeatmap.ControlPointInfo.DeepClone(); } catch (Exception e) { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 35782c6104..b4b1a09c4f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -242,7 +242,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { new OsuMenuItem("Create copy", MenuItemType.Standard, () => { - parentScreen?.OpenNewRoom(Room.CreateCopy()); + parentScreen?.OpenNewRoom(Room.DeepClone()); }) }; } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 2c46f76737..be28de5c43 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -72,8 +72,8 @@ namespace osu.Game.Screens.OnlinePlay // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. // Similarly, freeMods is currently empty but should only contain the allowed mods. - Mods.Value = selectedItem?.Value?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); - FreeMods.Value = selectedItem?.Value?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + Mods.Value = selectedItem?.Value?.RequiredMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty(); + FreeMods.Value = selectedItem?.Value?.AllowedMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty(); Mods.BindValueChanged(onModsChanged); Ruleset.BindValueChanged(onRulesetChanged); @@ -108,8 +108,8 @@ namespace osu.Game.Screens.OnlinePlay } }; - item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); - item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.CreateCopy())); + item.RequiredMods.AddRange(Mods.Value.Select(m => m.DeepClone())); + item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.DeepClone())); SelectItem(item); return true; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs index 21335fc90c..076fa77336 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs @@ -55,10 +55,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists item.Ruleset.Value = Ruleset.Value; item.RequiredMods.Clear(); - item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); + item.RequiredMods.AddRange(Mods.Value.Select(m => m.DeepClone())); item.AllowedMods.Clear(); - item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.CreateCopy())); + item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.DeepClone())); } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 97854ee12f..51f1dbd121 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -184,7 +184,7 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuConfigManager config, OsuGameBase game) { - Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); + Mods.Value = base.Mods.Value.Select(m => m.DeepClone()).ToArray(); if (Beatmap.Value is DummyWorkingBeatmap) return; diff --git a/osu.Game/Utils/IDeepCloneable.cs b/osu.Game/Utils/IDeepCloneable.cs new file mode 100644 index 0000000000..6877f346c4 --- /dev/null +++ b/osu.Game/Utils/IDeepCloneable.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. + +namespace osu.Game.Utils +{ + /// A generic interface for a deeply cloneable type. + /// The type of object to clone. + public interface IDeepCloneable where T : class + { + /// + /// Creates a new that is a deep copy of the current instance. + /// + /// The . + T DeepClone(); + } +} From 70c9d7105feb5162661379f427562de6cba17f91 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 19 Jul 2021 13:33:22 +0900 Subject: [PATCH 1586/1621] Add a function to compute hit object position in catch editor --- .../Edit/CatchHitObjectUtils.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs new file mode 100644 index 0000000000..beffdf0362 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.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 osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Edit +{ + /// + /// Utility functions used by the editor. + /// + public static class CatchHitObjectUtils + { + /// + /// Get the position of the hit object in the playfield based on and . + /// + public static Vector2 GetStartPosition(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject) + { + return new Vector2(hitObject.OriginalX, hitObjectContainer.PositionAtTime(hitObject.StartTime)); + } + } +} From c34758485125a3d0ad2f41d28c4c60e1771b2fc1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 19 Jul 2021 13:33:46 +0900 Subject: [PATCH 1587/1621] Use added utility function --- .../Edit/Blueprints/CatchSelectionBlueprint.cs | 5 ++--- .../Edit/Blueprints/Components/FruitOutline.cs | 6 +----- .../Edit/Blueprints/Components/NestedOutlineContainer.cs | 9 ++------- .../Edit/Blueprints/Components/ScrollingPath.cs | 6 ------ .../Edit/Blueprints/FruitPlacementBlueprint.cs | 3 ++- .../Edit/Blueprints/FruitSelectionBlueprint.cs | 6 ++++-- .../Edit/Blueprints/JuiceStreamSelectionBlueprint.cs | 3 +-- 7 files changed, 12 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs index 720d730858..7e566c810c 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs @@ -19,9 +19,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { get { - float x = HitObject.OriginalX; - float y = HitObjectContainer.PositionAtTime(HitObject.StartTime); - return HitObjectContainer.ToScreenSpace(new Vector2(x, y + HitObjectContainer.DrawHeight)); + Vector2 position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject); + return HitObjectContainer.ToScreenSpace(position + new Vector2(0, HitObjectContainer.DrawHeight)); } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs index 345b59bdcd..0c03068e26 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs @@ -1,14 +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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Skinning.Default; -using osu.Game.Rulesets.UI.Scrolling; using osuTK; namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components @@ -28,10 +26,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components Colour = osuColour.Yellow; } - public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject, [CanBeNull] CatchHitObject parent = null) + public void UpdateFrom(CatchHitObject hitObject) { - X = hitObject.EffectiveX - (parent?.OriginalX ?? 0); - Y = hitObjectContainer.PositionAtTime(hitObject.StartTime, parent?.StartTime ?? hitObjectContainer.Time.Current); Scale = new Vector2(hitObject.Scale); } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs index 48d90e8b24..cf916b27a4 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs @@ -20,12 +20,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components Anchor = Anchor.BottomLeft; } - public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject) - { - X = parentHitObject.OriginalX; - Y = hitObjectContainer.PositionAtTime(parentHitObject.StartTime); - } - public void UpdateNestedObjectsFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject) { nestedHitObjects.Clear(); @@ -43,7 +37,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { var hitObject = nestedHitObjects[i]; var outline = (FruitOutline)InternalChildren[i]; - outline.UpdateFrom(hitObjectContainer, hitObject, parentHitObject); + outline.Position = CatchHitObjectUtils.GetStartPosition(hitObjectContainer, hitObject) - Position; + outline.UpdateFrom(hitObject); outline.Scale *= hitObject is Droplet ? 0.5f : 1; } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs index 96111beda4..109bf61ea5 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs @@ -33,12 +33,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components }; } - public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject) - { - X = hitObject.OriginalX; - Y = hitObjectContainer.PositionAtTime(hitObject.StartTime); - } - public void UpdatePathFrom(ScrollingHitObjectContainer hitObjectContainer, JuiceStream hitObject) { double distanceToYFactor = -hitObjectContainer.LengthAtTime(hitObject.StartTime, hitObject.StartTime + 1 / hitObject.Velocity); diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs index 0f28cf6786..e169e3b75c 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs @@ -29,7 +29,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { base.Update(); - outline.UpdateFrom(HitObjectContainer, HitObject); + outline.Position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject); + outline.UpdateFrom(HitObject); } protected override bool OnMouseDown(MouseDownEvent e) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs index 9665aac2fb..150297badb 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs @@ -20,8 +20,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { base.Update(); - if (IsSelected) - outline.UpdateFrom(HitObjectContainer, HitObject); + if (!IsSelected) return; + + outline.Position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject); + outline.UpdateFrom(HitObject); } } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index bf7b962e0a..0614c4c24d 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -49,8 +49,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints if (!IsSelected) return; - scrollingPath.UpdatePositionFrom(HitObjectContainer, HitObject); - nestedOutlineContainer.UpdatePositionFrom(HitObjectContainer, HitObject); + nestedOutlineContainer.Position = scrollingPath.Position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject); if (pathCache.IsValid) return; From be495a74887f12011df92911f1a72ff6d8b1b59e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 19 Jul 2021 13:03:53 +0900 Subject: [PATCH 1588/1621] Add `JuiceStreamPath` to allow editing `JuiceStream` in catch editor A `JuiceStream` holds a legacy `SliderPath` as the representation of the path. However, the `SliderPath` representation is difficult to work with because `SliderPath` works in 2D position, while osu!catch works in `(time, x)` coordinates. This `JuiceStreamPath` represents the path in a more convenient way, a polyline connecting list of `(distance, x)` vertices. --- .../Objects/JuiceStreamPath.cs | 340 ++++++++++++++++++ .../Objects/JuiceStreamPathVertex.cs | 33 ++ 2 files changed, 373 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs create mode 100644 osu.Game.Rulesets.Catch/Objects/JuiceStreamPathVertex.cs diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs new file mode 100644 index 0000000000..ac11bd9918 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs @@ -0,0 +1,340 @@ +// 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.Utils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +#nullable enable + +namespace osu.Game.Rulesets.Catch.Objects +{ + /// + /// Represents the path of a juice stream. + /// + /// A holds a legacy as the representation of the path. + /// However, the representation is difficult to work with. + /// This represents the path in a more convenient way, a polyline connecting list of s. + /// + /// + /// The path can be regarded as a function from the closed interval [Vertices[0].Distance, Vertices[^1].Distance] to the x position, given by . + /// To ensure the path is convertible to a , the slope of the function must not be more than 1 everywhere, + /// and this slope condition is always maintained as an invariant. + /// + /// + public class JuiceStreamPath + { + /// + /// The list of vertices of the path, which is represented as a polyline connecting the vertices. + /// + public IReadOnlyList Vertices => vertices; + + /// + /// The current version number. + /// This starts from 1 and incremented whenever this is modified. + /// + public int InvalidationID { get; private set; } = 1; + + /// + /// The difference between first vertex's and last vertex's . + /// + public double Distance => vertices[^1].Distance - vertices[0].Distance; + + /// + /// This list should always be non-empty. + /// + private readonly List vertices = new List + { + new JuiceStreamPathVertex() + }; + + /// + /// Compute the x-position of the path at the given . + /// + /// + /// When the given distance is outside of the path, the x position at the corresponding endpoint is returned, + /// + public float PositionAtDistance(double distance) + { + int index = vertexIndexAtDistance(distance); + return positionAtDistance(distance, index); + } + + /// + /// Remove all vertices of this path, then add a new vertex (0, 0). + /// + public void Clear() + { + vertices.Clear(); + vertices.Add(new JuiceStreamPathVertex()); + invalidate(); + } + + /// + /// Insert a vertex at given . + /// The is used as the position of the new vertex. + /// Thus, the set of points of the path is not changed (up to floating-point precision). + /// + /// The index of the new vertex. + public int InsertVertex(double distance) + { + if (!double.IsFinite(distance)) + throw new ArgumentOutOfRangeException(nameof(distance)); + + int index = vertexIndexAtDistance(distance); + float x = positionAtDistance(distance, index); + vertices.Insert(index, new JuiceStreamPathVertex(distance, x)); + + invalidate(); + return index; + } + + /// + /// Move the vertex of given to the given position . + /// When the distances between vertices are too small for the new vertex positions, the adjacent vertices are moved towards . + /// + public void SetVertexPosition(int index, float newX) + { + if (index < 0 || index >= vertices.Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (!float.IsFinite(newX)) + throw new ArgumentOutOfRangeException(nameof(newX)); + + var newVertex = new JuiceStreamPathVertex(vertices[index].Distance, newX); + + for (int i = index - 1; i >= 0 && !canConnect(vertices[i], newVertex); i--) + { + float clampedX = clampToConnectablePosition(newVertex, vertices[i]); + vertices[i] = new JuiceStreamPathVertex(vertices[i].Distance, clampedX); + } + + for (int i = index + 1; i < vertices.Count; i++) + { + float clampedX = clampToConnectablePosition(newVertex, vertices[i]); + vertices[i] = new JuiceStreamPathVertex(vertices[i].Distance, clampedX); + } + + vertices[index] = newVertex; + + invalidate(); + } + + /// + /// Add a new vertex at given and position. + /// Adjacent vertices are moved when necessary in the same way as . + /// + public void Add(double distance, float x) + { + int index = InsertVertex(distance); + SetVertexPosition(index, x); + } + + /// + /// Remove all vertices that satisfy the given . + /// + /// + /// If all vertices are removed, a new vertex (0, 0) is added. + /// + /// The predicate to determine whether a vertex should be removed given the vertex and its index in the path. + /// The number of removed vertices. + public int RemoveVertices(Func predicate) + { + int index = 0; + int removeCount = vertices.RemoveAll(vertex => predicate(vertex, index++)); + + if (vertices.Count == 0) + vertices.Add(new JuiceStreamPathVertex()); + + if (removeCount != 0) + invalidate(); + + return removeCount; + } + + /// + /// Recreate this path by using difference set of vertices at given distances. + /// In addition to the given , the first vertex and the last vertex are always added to the new path. + /// New vertices use the positions on the original path. Thus, s at are preserved. + /// + public void ResampleVertices(IEnumerable sampleDistances) + { + var sampledVertices = new List(); + + foreach (double distance in sampleDistances) + { + if (!double.IsFinite(distance)) + throw new ArgumentOutOfRangeException(nameof(sampleDistances)); + + double clampedDistance = Math.Clamp(distance, vertices[0].Distance, vertices[^1].Distance); + float x = PositionAtDistance(clampedDistance); + sampledVertices.Add(new JuiceStreamPathVertex(clampedDistance, x)); + } + + sampledVertices.Sort(); + + // The first vertex and the last vertex are always used in the result. + vertices.RemoveRange(1, vertices.Count - (vertices.Count == 1 ? 1 : 2)); + vertices.InsertRange(1, sampledVertices); + + invalidate(); + } + + /// + /// Convert a to list of vertices and write the result to this . + /// + /// + /// Duplicated vertices are automatically removed. + /// + public void ConvertFromSliderPath(SliderPath sliderPath) + { + var sliderPathVertices = new List(); + sliderPath.GetPathToProgress(sliderPathVertices, 0, 1); + + double distance = 0; + + vertices.Clear(); + vertices.Add(new JuiceStreamPathVertex(0, sliderPathVertices.FirstOrDefault().X)); + + for (int i = 1; i < sliderPathVertices.Count; i++) + { + distance += Vector2.Distance(sliderPathVertices[i - 1], sliderPathVertices[i]); + + if (!Precision.AlmostEquals(vertices[^1].Distance, distance)) + vertices.Add(new JuiceStreamPathVertex(distance, sliderPathVertices[i].X)); + } + + invalidate(); + } + + /// + /// The height of legacy osu!standard playfield. + /// The sliders converted by are vertically contained in this height. + /// + public const float OSU_PLAYFIELD_HEIGHT = 384; + + /// + /// Convert the path of this to a and write the result to . + /// The resulting slider is "folded" to make it vertically contained in the playfield `(0..)` assuming the slider start position is . + /// + public void ConvertToSliderPath(SliderPath sliderPath, float sliderStartY) + { + const float margin = 1; + + // Note: these two variables and `sliderPath` are modified by the local functions. + double currentDistance = 0; + Vector2 lastPosition = new Vector2(vertices[0].X, 0); + + sliderPath.ControlPoints.Clear(); + sliderPath.ControlPoints.Add(new PathControlPoint(lastPosition)); + + for (int i = 1; i < vertices.Count; i++) + { + sliderPath.ControlPoints[^1].Type.Value = PathType.Linear; + + float deltaX = vertices[i].X - lastPosition.X; + double length = vertices[i].Distance - currentDistance; + + // Should satisfy `deltaX^2 + deltaY^2 = length^2`. + // By invariants, the expression inside the `sqrt` is (almost) non-negative. + double deltaY = Math.Sqrt(Math.Max(0, length * length - (double)deltaX * deltaX)); + + // When `deltaY` is small, one segment is always enough. + // This case is handled separately to prevent divide-by-zero. + if (deltaY <= OSU_PLAYFIELD_HEIGHT / 2 - margin) + { + float nextX = vertices[i].X; + float nextY = (float)(lastPosition.Y + getYDirection() * deltaY); + addControlPoint(nextX, nextY); + continue; + } + + // When `deltaY` is large or when the slider velocity is fast, the segment must be partitioned to subsegments to stay in bounds. + for (double currentProgress = 0; currentProgress < deltaY;) + { + double nextProgress = Math.Min(currentProgress + getMaxDeltaY(), deltaY); + float nextX = (float)(vertices[i - 1].X + nextProgress / deltaY * deltaX); + float nextY = (float)(lastPosition.Y + getYDirection() * (nextProgress - currentProgress)); + addControlPoint(nextX, nextY); + currentProgress = nextProgress; + } + } + + int getYDirection() + { + float lastSliderY = sliderStartY + lastPosition.Y; + return lastSliderY < OSU_PLAYFIELD_HEIGHT / 2 ? 1 : -1; + } + + float getMaxDeltaY() + { + float lastSliderY = sliderStartY + lastPosition.Y; + return Math.Max(lastSliderY, OSU_PLAYFIELD_HEIGHT - lastSliderY) - margin; + } + + void addControlPoint(float nextX, float nextY) + { + Vector2 nextPosition = new Vector2(nextX, nextY); + sliderPath.ControlPoints.Add(new PathControlPoint(nextPosition)); + currentDistance += Vector2.Distance(lastPosition, nextPosition); + lastPosition = nextPosition; + } + } + + /// + /// Find the index at which a new vertex with can be inserted. + /// + private int vertexIndexAtDistance(double distance) + { + // The position of `(distance, Infinity)` is uniquely determined because infinite positions are not allowed. + int i = vertices.BinarySearch(new JuiceStreamPathVertex(distance, float.PositiveInfinity)); + return i < 0 ? ~i : i; + } + + /// + /// Compute the position at the given , assuming is the vertex index returned by . + /// + private float positionAtDistance(double distance, int index) + { + if (index <= 0) + return vertices[0].X; + if (index >= vertices.Count) + return vertices[^1].X; + + double length = vertices[index].Distance - vertices[index - 1].Distance; + if (Precision.AlmostEquals(length, 0)) + return vertices[index].X; + + float deltaX = vertices[index].X - vertices[index - 1].X; + + return (float)(vertices[index - 1].X + deltaX * ((distance - vertices[index - 1].Distance) / length)); + } + + /// + /// Check the two vertices can connected directly while satisfying the slope condition. + /// + private bool canConnect(JuiceStreamPathVertex vertex1, JuiceStreamPathVertex vertex2, float allowance = 0) + { + double xDistance = Math.Abs((double)vertex2.X - vertex1.X); + float length = (float)Math.Abs(vertex2.Distance - vertex1.Distance); + return xDistance <= length + allowance; + } + + /// + /// Move the position of towards the position of + /// until the vertex pair satisfies the condition . + /// + /// The resulting position of . + private float clampToConnectablePosition(JuiceStreamPathVertex fixedVertex, JuiceStreamPathVertex movableVertex) + { + float length = (float)Math.Abs(movableVertex.Distance - fixedVertex.Distance); + return Math.Clamp(movableVertex.X, fixedVertex.X - length, fixedVertex.X + length); + } + + private void invalidate() => InvalidationID++; + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPathVertex.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPathVertex.cs new file mode 100644 index 0000000000..58c50603c4 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPathVertex.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; + +#nullable enable + +namespace osu.Game.Rulesets.Catch.Objects +{ + /// + /// A vertex of a . + /// + public readonly struct JuiceStreamPathVertex : IComparable + { + public readonly double Distance; + + public readonly float X; + + public JuiceStreamPathVertex(double distance, float x) + { + Distance = distance; + X = x; + } + + public int CompareTo(JuiceStreamPathVertex other) + { + int c = Distance.CompareTo(other.Distance); + return c != 0 ? c : X.CompareTo(other.X); + } + + public override string ToString() => $"({Distance}, {X})"; + } +} From fffe0d2e5735d68c7beef525b5c134fe5822ba69 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 19 Jul 2021 13:09:33 +0900 Subject: [PATCH 1589/1621] Add tests of `JuiceStreamPath`. "Property testing" is heavily used, tests that generates random cases and asserting properties. That gives high confidence of round-trip correctness of `SliderPath` conversion, for example. --- .../JuiceStreamPathTest.cs | 288 ++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs diff --git a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs new file mode 100644 index 0000000000..5e4b6d9e1a --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs @@ -0,0 +1,288 @@ +// 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.Utils; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class JuiceStreamPathTest + { + [TestCase(1e3, true, false)] + // When the coordinates are large, the slope invariant fails within the specified absolute allowance due to the floating-number precision. + [TestCase(1e9, false, false)] + // Using discrete values sometimes discover more edge cases. + [TestCase(10, true, true)] + public void TestRandomInsertSetPosition(double scale, bool checkSlope, bool integralValues) + { + var rng = new Random(1); + var path = new JuiceStreamPath(); + + for (int iteration = 0; iteration < 100000; iteration++) + { + if (rng.Next(10) == 0) + path.Clear(); + + int vertexCount = path.Vertices.Count; + + switch (rng.Next(2)) + { + case 0: + { + double distance = rng.NextDouble() * scale * 2 - scale; + if (integralValues) + distance = Math.Round(distance); + + float oldX = path.PositionAtDistance(distance); + int index = path.InsertVertex(distance); + Assert.That(path.Vertices.Count, Is.EqualTo(vertexCount + 1)); + Assert.That(path.Vertices[index].Distance, Is.EqualTo(distance)); + Assert.That(path.Vertices[index].X, Is.EqualTo(oldX)); + break; + } + + case 1: + { + int index = rng.Next(path.Vertices.Count); + double distance = path.Vertices[index].Distance; + float newX = (float)(rng.NextDouble() * scale * 2 - scale); + if (integralValues) + newX = MathF.Round(newX); + + path.SetVertexPosition(index, newX); + Assert.That(path.Vertices.Count, Is.EqualTo(vertexCount)); + Assert.That(path.Vertices[index].Distance, Is.EqualTo(distance)); + Assert.That(path.Vertices[index].X, Is.EqualTo(newX)); + break; + } + } + + assertInvariants(path.Vertices, checkSlope); + } + } + + [Test] + public void TestRemoveVertices() + { + var path = new JuiceStreamPath(); + path.Add(10, 5); + path.Add(20, -5); + + int removeCount = path.RemoveVertices((v, i) => v.Distance == 10 && i == 1); + Assert.That(removeCount, Is.EqualTo(1)); + Assert.That(path.Vertices, Is.EqualTo(new[] + { + new JuiceStreamPathVertex(0, 0), + new JuiceStreamPathVertex(20, -5) + })); + + removeCount = path.RemoveVertices((_, i) => i == 0); + Assert.That(removeCount, Is.EqualTo(1)); + Assert.That(path.Vertices, Is.EqualTo(new[] + { + new JuiceStreamPathVertex(20, -5) + })); + + removeCount = path.RemoveVertices((_, i) => true); + Assert.That(removeCount, Is.EqualTo(1)); + Assert.That(path.Vertices, Is.EqualTo(new[] + { + new JuiceStreamPathVertex() + })); + } + + [Test] + public void TestResampleVertices() + { + var path = new JuiceStreamPath(); + path.Add(-100, -10); + path.Add(100, 50); + path.ResampleVertices(new double[] + { + -50, + 0, + 70, + 120 + }); + Assert.That(path.Vertices, Is.EqualTo(new[] + { + new JuiceStreamPathVertex(-100, -10), + new JuiceStreamPathVertex(-50, -5), + new JuiceStreamPathVertex(0, 0), + new JuiceStreamPathVertex(70, 35), + new JuiceStreamPathVertex(100, 50), + new JuiceStreamPathVertex(100, 50), + })); + + path.Clear(); + path.SetVertexPosition(0, 10); + path.ResampleVertices(Array.Empty()); + Assert.That(path.Vertices, Is.EqualTo(new[] + { + new JuiceStreamPathVertex(0, 10) + })); + } + + [Test] + public void TestRandomConvertFromSliderPath() + { + var rng = new Random(1); + var path = new JuiceStreamPath(); + var sliderPath = new SliderPath(); + + for (int iteration = 0; iteration < 10000; iteration++) + { + sliderPath.ControlPoints.Clear(); + + do + { + int start = sliderPath.ControlPoints.Count; + + do + { + float x = (float)(rng.NextDouble() * 1e3); + float y = (float)(rng.NextDouble() * 1e3); + sliderPath.ControlPoints.Add(new PathControlPoint(new Vector2(x, y))); + } while (rng.Next(2) != 0); + + int length = sliderPath.ControlPoints.Count - start + 1; + sliderPath.ControlPoints[start].Type.Value = length <= 2 ? PathType.Linear : length == 3 ? PathType.PerfectCurve : PathType.Bezier; + } while (rng.Next(3) != 0); + + if (rng.Next(5) == 0) + sliderPath.ExpectedDistance.Value = rng.NextDouble() * 3e3; + else + sliderPath.ExpectedDistance.Value = null; + + path.ConvertFromSliderPath(sliderPath); + Assert.That(path.Vertices[0].Distance, Is.EqualTo(0)); + Assert.That(path.Distance, Is.EqualTo(sliderPath.Distance).Within(1e-3)); + assertInvariants(path.Vertices, true); + + double[] sampleDistances = Enumerable.Range(0, 10) + .Select(_ => rng.NextDouble() * sliderPath.Distance) + .ToArray(); + + foreach (double distance in sampleDistances) + { + float expected = sliderPath.PositionAt(distance / sliderPath.Distance).X; + Assert.That(path.PositionAtDistance(distance), Is.EqualTo(expected).Within(1e-3)); + } + + path.ResampleVertices(sampleDistances); + assertInvariants(path.Vertices, true); + + foreach (double distance in sampleDistances) + { + float expected = sliderPath.PositionAt(distance / sliderPath.Distance).X; + Assert.That(path.PositionAtDistance(distance), Is.EqualTo(expected).Within(1e-3)); + } + } + } + + [Test] + public void TestRandomConvertToSliderPath() + { + var rng = new Random(1); + var path = new JuiceStreamPath(); + var sliderPath = new SliderPath(); + + for (int iteration = 0; iteration < 10000; iteration++) + { + path.Clear(); + + do + { + double distance = rng.NextDouble() * 1e3; + float x = (float)(rng.NextDouble() * 1e3); + path.Add(distance, x); + } while (rng.Next(5) != 0); + + float sliderStartY = (float)(rng.NextDouble() * JuiceStreamPath.OSU_PLAYFIELD_HEIGHT); + + path.ConvertToSliderPath(sliderPath, sliderStartY); + Assert.That(sliderPath.Distance, Is.EqualTo(path.Distance).Within(1e-3)); + Assert.That(sliderPath.ControlPoints[0].Position.Value.X, Is.EqualTo(path.Vertices[0].X)); + assertInvariants(path.Vertices, true); + + foreach (var point in sliderPath.ControlPoints) + { + Assert.That(point.Type.Value, Is.EqualTo(PathType.Linear).Or.Null); + Assert.That(sliderStartY + point.Position.Value.Y, Is.InRange(0, JuiceStreamPath.OSU_PLAYFIELD_HEIGHT)); + } + + for (int i = 0; i < 10; i++) + { + double distance = rng.NextDouble() * path.Distance; + float expected = path.PositionAtDistance(distance); + Assert.That(sliderPath.PositionAt(distance / sliderPath.Distance).X, Is.EqualTo(expected).Within(1e-3)); + } + } + } + + [Test] + public void TestInvalidation() + { + var path = new JuiceStreamPath(); + Assert.That(path.InvalidationID, Is.EqualTo(1)); + int previousId = path.InvalidationID; + + path.InsertVertex(10); + checkNewId(); + + path.SetVertexPosition(1, 5); + checkNewId(); + + path.Add(20, 0); + checkNewId(); + + path.RemoveVertices((v, _) => v.Distance == 20); + checkNewId(); + + path.ResampleVertices(new double[] { 5, 10, 15 }); + checkNewId(); + + path.Clear(); + checkNewId(); + + path.ConvertFromSliderPath(new SliderPath()); + checkNewId(); + + void checkNewId() + { + Assert.That(path.InvalidationID, Is.Not.EqualTo(previousId)); + previousId = path.InvalidationID; + } + } + + private void assertInvariants(IReadOnlyList vertices, bool checkSlope) + { + Assert.That(vertices, Is.Not.Empty); + + for (int i = 0; i < vertices.Count; i++) + { + Assert.That(double.IsFinite(vertices[i].Distance)); + Assert.That(float.IsFinite(vertices[i].X)); + } + + for (int i = 1; i < vertices.Count; i++) + { + Assert.That(vertices[i].Distance, Is.GreaterThanOrEqualTo(vertices[i - 1].Distance)); + + if (!checkSlope) continue; + + float xDiff = Math.Abs(vertices[i].X - vertices[i - 1].X); + double distanceDiff = vertices[i].Distance - vertices[i - 1].Distance; + Assert.That(xDiff, Is.LessThanOrEqualTo(distanceDiff).Within(Precision.FLOAT_EPSILON)); + } + } + } +} From e507faef294748c4aac5686aef06fa343e83069b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 13:02:40 +0900 Subject: [PATCH 1590/1621] Add deep cloning support to `Score`/`ScoreInfo`/`Replay` --- osu.Game.Tests/NonVisual/ScoreInfoTest.cs | 33 +++++++++++++++++++++++ osu.Game/Replays/Replay.cs | 14 +++++++++- osu.Game/Scoring/Score.cs | 12 ++++++++- osu.Game/Scoring/ScoreInfo.cs | 11 +++++++- 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/NonVisual/ScoreInfoTest.cs diff --git a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs new file mode 100644 index 0000000000..6e5718cd4c --- /dev/null +++ b/osu.Game.Tests/NonVisual/ScoreInfoTest.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 NUnit.Framework; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class ScoreInfoTest + { + [Test] + public void TestDeepClone() + { + var score = new ScoreInfo(); + + score.Statistics.Add(HitResult.Good, 10); + score.Rank = ScoreRank.B; + + var scoreCopy = score.DeepClone(); + + score.Statistics[HitResult.Good]++; + score.Rank = ScoreRank.X; + + Assert.That(scoreCopy.Statistics[HitResult.Good], Is.EqualTo(10)); + Assert.That(score.Statistics[HitResult.Good], Is.EqualTo(11)); + + Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B)); + Assert.That(score.Rank, Is.EqualTo(ScoreRank.X)); + } + } +} diff --git a/osu.Game/Replays/Replay.cs b/osu.Game/Replays/Replay.cs index 5430915394..30e176b5c7 100644 --- a/osu.Game/Replays/Replay.cs +++ b/osu.Game/Replays/Replay.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Game.Rulesets.Replays; +using osu.Game.Utils; namespace osu.Game.Replays { - public class Replay + public class Replay : IDeepCloneable { /// /// Whether all frames for this replay have been received. @@ -15,5 +17,15 @@ namespace osu.Game.Replays public bool HasReceivedAllFrames = true; public List Frames = new List(); + + public Replay DeepClone() + { + return new Replay + { + HasReceivedAllFrames = HasReceivedAllFrames, + // individual frames are mutable for now but hopefully this will not be a thing in the future. + Frames = Frames.ToList(), + }; + } } } diff --git a/osu.Game/Scoring/Score.cs b/osu.Game/Scoring/Score.cs index 4e82b1584e..83e4389dc8 100644 --- a/osu.Game/Scoring/Score.cs +++ b/osu.Game/Scoring/Score.cs @@ -2,12 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Replays; +using osu.Game.Utils; namespace osu.Game.Scoring { - public class Score + public class Score : IDeepCloneable { public ScoreInfo ScoreInfo = new ScoreInfo(); public Replay Replay = new Replay(); + + public Score DeepClone() + { + return new Score + { + ScoreInfo = ScoreInfo.DeepClone(), + Replay = Replay.DeepClone(), + }; + } } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 1d22dab0af..a0c4d5a026 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -18,7 +18,7 @@ using osu.Game.Utils; namespace osu.Game.Scoring { - public class ScoreInfo : IHasFiles, IHasPrimaryKey, ISoftDelete, IEquatable + public class ScoreInfo : IHasFiles, IHasPrimaryKey, ISoftDelete, IEquatable, IDeepCloneable { public int ID { get; set; } @@ -242,6 +242,15 @@ namespace osu.Game.Scoring } } + public ScoreInfo DeepClone() + { + var clone = (ScoreInfo)MemberwiseClone(); + + clone.Statistics = new Dictionary(clone.Statistics); + + return clone; + } + public override string ToString() => $"{User} playing {Beatmap}"; public bool Equals(ScoreInfo other) From caba78cb5d0ebd67053537e634d613fae733933c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 13:04:16 +0900 Subject: [PATCH 1591/1621] Copy score during submission process to ensure it isn't modified --- osu.Game/Screens/Play/SoloPlayer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index d90e8e0168..7d15fcf49f 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -38,13 +38,15 @@ namespace osu.Game.Screens.Play protected override APIRequest CreateSubmissionRequest(Score score, long token) { - var beatmap = score.ScoreInfo.Beatmap; + var scoreCopy = score.DeepClone(); + + var beatmap = scoreCopy.ScoreInfo.Beatmap; Debug.Assert(beatmap.OnlineBeatmapID != null); int beatmapId = beatmap.OnlineBeatmapID.Value; - return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); + return new SubmitSoloScoreRequest(beatmapId, token, scoreCopy.ScoreInfo); } } } From 87c39909c6c3ab7f872b33a3bb20714144022ac9 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 19 Jul 2021 14:37:19 +0900 Subject: [PATCH 1592/1621] Simplify `DependencyProvidingContainer` Using an array of tuple for dependencies instead of using children. --- .../TestSceneCatcher.cs | 7 +++--- .../Visual/DependencyProvidingContainer.cs | 24 ++++--------------- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 0cf5e2b7a0..0a2dff6a21 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -47,15 +47,16 @@ namespace osu.Game.Rulesets.Catch.Tests { Anchor = Anchor.Centre, }; + droppedObjectContainer = new DroppedObjectContainer(); Child = new DependencyProvidingContainer { - Types = new[] + CachedDependencies = new (Type, object)[] { - typeof(DroppedObjectContainer), + (typeof(DroppedObjectContainer), droppedObjectContainer), }, Children = new Drawable[] { - droppedObjectContainer = new DroppedObjectContainer(), + droppedObjectContainer, catcher = new TestCatcher(trailContainer, difficulty), trailContainer }, diff --git a/osu.Game/Tests/Visual/DependencyProvidingContainer.cs b/osu.Game/Tests/Visual/DependencyProvidingContainer.cs index cd3ae1123b..c799cad61a 100644 --- a/osu.Game/Tests/Visual/DependencyProvidingContainer.cs +++ b/osu.Game/Tests/Visual/DependencyProvidingContainer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; @@ -10,39 +9,24 @@ namespace osu.Game.Tests.Visual { /// /// A which providing ad-hoc dependencies to the child drawables. - /// - /// To provide a dependency, specify the dependency type to , then specify the dependency value to either or . - /// For each type specified in , the first value compatible with the type is selected and provided to the children. - /// /// /// - /// The and values of the dependencies must be set while this is not loaded. + /// The must be set while this is not loaded. /// public class DependencyProvidingContainer : Container { /// - /// The types of the dependencies provided to the children. + /// The dependencies provided to the children. /// // TODO: should be an init-only property when C# 9 - public Type[] Types { get; set; } = Array.Empty(); - - /// - /// The dependency values provided to the children. - /// - public object[] Values { get; set; } = Array.Empty(); + public (Type, object)[] CachedDependencies { get; set; } = Array.Empty<(Type, object)>(); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencyContainer = new DependencyContainer(base.CreateChildDependencies(parent)); - foreach (var type in Types) - { - object value = Values.FirstOrDefault(v => type.IsInstanceOfType(v)) ?? - Children.FirstOrDefault(d => type.IsInstanceOfType(d)) ?? - throw new InvalidOperationException($"The type {type} is specified in this {nameof(DependencyProvidingContainer)}, but no corresponding value is provided."); - + foreach (var (type, value) in CachedDependencies) dependencyContainer.CacheAs(type, value); - } return dependencyContainer; } From bde35d9f211e9cd789eb6499e7321b33f87d1f31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 16:57:12 +0900 Subject: [PATCH 1593/1621] Rename radio button classes to be local to editor --- .../Editing/TestSceneEditorComposeRadioButtons.cs | 4 ++-- .../Visual/Editing/TestSceneHitObjectComposer.cs | 6 +++--- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++-- .../{DrawableRadioButton.cs => EditorRadioButton.cs} | 6 +++--- ...tonCollection.cs => EditorRadioButtonCollection.cs} | 10 +++++----- 5 files changed, 15 insertions(+), 15 deletions(-) rename osu.Game/Screens/Edit/Components/RadioButtons/{DrawableRadioButton.cs => EditorRadioButton.cs} (94%) rename osu.Game/Screens/Edit/Components/RadioButtons/{RadioButtonCollection.cs => EditorRadioButtonCollection.cs} (85%) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs index 0b52ae2b95..028509ccd4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs @@ -13,8 +13,8 @@ namespace osu.Game.Tests.Visual.Editing { public TestSceneEditorComposeRadioButtons() { - RadioButtonCollection collection; - Add(collection = new RadioButtonCollection + EditorRadioButtonCollection collection; + Add(collection = new EditorRadioButtonCollection { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index 67c37413ed..550896270a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -72,10 +72,10 @@ namespace osu.Game.Tests.Visual.Editing AddStep("clear all control points", () => editorBeatmap.ControlPointInfo.Clear()); AddAssert("Tool is selection", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is SelectTool); - AddAssert("Hitcircle button not clickable", () => !hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Enabled.Value); + AddAssert("Hitcircle button not clickable", () => !hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Enabled.Value); AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); - AddAssert("Hitcircle button is clickable", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Enabled.Value); - AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Click()); + AddAssert("Hitcircle button is clickable", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Enabled.Value); + AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").Click()); AddAssert("Tool changed", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is HitCircleCompositionTool); } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c6c112bec8..7ee631674e 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Edit private InputManager inputManager; - private RadioButtonCollection toolboxCollection; + private EditorRadioButtonCollection toolboxCollection; private FillFlowContainer togglesCollection; @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Edit { new ToolboxGroup("toolbox (1-9)") { - Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } + Child = toolboxCollection = new EditorRadioButtonCollection { RelativeSizeAxes = Axes.X } }, new ToolboxGroup("toggles (Q~P)") { diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs similarity index 94% rename from osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs rename to osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs index bbe77eaa07..d66856ebd8 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs @@ -18,10 +18,10 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.RadioButtons { - public class DrawableRadioButton : OsuButton, IHasTooltip + public class EditorRadioButton : OsuButton, IHasTooltip { /// - /// Invoked when this has been selected. + /// Invoked when this has been selected. /// public Action Selected; @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons [Resolved(canBeNull: true)] private EditorBeatmap editorBeatmap { get; set; } - public DrawableRadioButton(RadioButton button) + public EditorRadioButton(RadioButton button) { Button = button; diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButtonCollection.cs similarity index 85% rename from osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs rename to osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButtonCollection.cs index 16574c0baf..6a7b0c9ef7 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButtonCollection.cs @@ -9,7 +9,7 @@ using osuTK; namespace osu.Game.Screens.Edit.Components.RadioButtons { - public class RadioButtonCollection : CompositeDrawable + public class EditorRadioButtonCollection : CompositeDrawable { private IReadOnlyList items; @@ -28,13 +28,13 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons } } - private readonly FlowContainer buttonContainer; + private readonly FlowContainer buttonContainer; - public RadioButtonCollection() + public EditorRadioButtonCollection() { AutoSizeAxes = Axes.Y; - InternalChild = buttonContainer = new FillFlowContainer + InternalChild = buttonContainer = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons currentlySelected = null; }; - buttonContainer.Add(new DrawableRadioButton(button)); + buttonContainer.Add(new EditorRadioButton(button)); } } } From b29209d13f6bd66b52f5d2c122001392691dd3c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 17:08:40 +0900 Subject: [PATCH 1594/1621] Ensure tool is always set back to select tool when beatmap becomes untimed --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 7ee631674e..8090fcbd32 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -67,6 +68,8 @@ namespace osu.Game.Rulesets.Edit private FillFlowContainer togglesCollection; + private IBindable hasTiming; + protected HitObjectComposer(Ruleset ruleset) { Ruleset = ruleset; @@ -160,6 +163,14 @@ namespace osu.Game.Rulesets.Edit base.LoadComplete(); inputManager = GetContainingInputManager(); + + hasTiming = EditorBeatmap.HasTiming.GetBoundCopy(); + hasTiming.BindValueChanged(timing => + { + // it's important this is performed before the similar code in EditorRadioButton disables the button. + if (!timing.NewValue) + setSelectTool(); + }); } public override Playfield Playfield => drawableRulesetWrapper.Playfield; From 13cb658d29e4372b5f986f1caad46adc2d28be91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 18:15:09 +0900 Subject: [PATCH 1595/1621] Mark identifiers as verbatim strings --- osu.Game/Overlays/Profile/Sections/AboutSection.cs | 2 +- osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs | 2 +- osu.Game/Overlays/Profile/Sections/HistoricalSection.cs | 2 +- osu.Game/Overlays/Profile/Sections/KudosuSection.cs | 2 +- osu.Game/Overlays/Profile/Sections/MedalsSection.cs | 2 +- osu.Game/Overlays/Profile/Sections/RanksSection.cs | 2 +- osu.Game/Overlays/Profile/Sections/RecentSection.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/AboutSection.cs b/osu.Game/Overlays/Profile/Sections/AboutSection.cs index c224d2b1be..d0d9362fd2 100644 --- a/osu.Game/Overlays/Profile/Sections/AboutSection.cs +++ b/osu.Game/Overlays/Profile/Sections/AboutSection.cs @@ -10,6 +10,6 @@ namespace osu.Game.Overlays.Profile.Sections { public override LocalisableString Title => UsersStrings.ShowExtraMeTitle; - public override string Identifier => "me"; + public override string Identifier => @"me"; } } diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs index b8fbe73c0c..843ab531be 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Profile.Sections { public override LocalisableString Title => UsersStrings.ShowExtraBeatmapsTitle; - public override string Identifier => "beatmaps"; + public override string Identifier => @"beatmaps"; public BeatmapsSection() { diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index cba25c0a8b..203844b6b5 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Profile.Sections { public override LocalisableString Title => UsersStrings.ShowExtraHistoricalTitle; - public override string Identifier => "historical"; + public override string Identifier => @"historical"; public HistoricalSection() { diff --git a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs index ffa7ef4eaf..5b749c78a8 100644 --- a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs +++ b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Profile.Sections { public override LocalisableString Title => UsersStrings.ShowExtraKudosuTitle; - public override string Identifier => "kudosu"; + public override string Identifier => @"kudosu"; public KudosuSection() { diff --git a/osu.Game/Overlays/Profile/Sections/MedalsSection.cs b/osu.Game/Overlays/Profile/Sections/MedalsSection.cs index 3512333e27..cacdd44b61 100644 --- a/osu.Game/Overlays/Profile/Sections/MedalsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/MedalsSection.cs @@ -10,6 +10,6 @@ namespace osu.Game.Overlays.Profile.Sections { public override LocalisableString Title => UsersStrings.ShowExtraMedalsTitle; - public override string Identifier => "medals"; + public override string Identifier => @"medals"; } } diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 5e0648dbb1..00a68d5bf9 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Profile.Sections { public override LocalisableString Title => UsersStrings.ShowExtraTopRanksTitle; - public override string Identifier => "top_ranks"; + public override string Identifier => @"top_ranks"; public RanksSection() { diff --git a/osu.Game/Overlays/Profile/Sections/RecentSection.cs b/osu.Game/Overlays/Profile/Sections/RecentSection.cs index 7a6536c3af..33d435aa1b 100644 --- a/osu.Game/Overlays/Profile/Sections/RecentSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RecentSection.cs @@ -11,7 +11,7 @@ namespace osu.Game.Overlays.Profile.Sections { public override LocalisableString Title => UsersStrings.ShowExtraRecentActivityTitle; - public override string Identifier => "recent_activity"; + public override string Identifier => @"recent_activity"; public RecentSection() { From 443058f87994c2db0d8cbe031fd943c75f7d67e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 18:41:29 +0900 Subject: [PATCH 1596/1621] Move playfield constant to top of file and make `internal` --- osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs index ac11bd9918..f1cdb39e91 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs @@ -28,6 +28,12 @@ namespace osu.Game.Rulesets.Catch.Objects /// public class JuiceStreamPath { + /// + /// The height of legacy osu!standard playfield. + /// The sliders converted by are vertically contained in this height. + /// + internal const float OSU_PLAYFIELD_HEIGHT = 384; + /// /// The list of vertices of the path, which is represented as a polyline connecting the vertices. /// @@ -211,12 +217,6 @@ namespace osu.Game.Rulesets.Catch.Objects invalidate(); } - /// - /// The height of legacy osu!standard playfield. - /// The sliders converted by are vertically contained in this height. - /// - public const float OSU_PLAYFIELD_HEIGHT = 384; - /// /// Convert the path of this to a and write the result to . /// The resulting slider is "folded" to make it vertically contained in the playfield `(0..)` assuming the slider start position is . From f16b4957aae84870d90c7e964e972779d01e702e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 19:18:04 +0900 Subject: [PATCH 1597/1621] Move clone to earlier in the process --- osu.Game/Screens/Play/Player.cs | 8 +++++--- osu.Game/Screens/Play/SoloPlayer.cs | 6 ++---- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 51f1dbd121..2328390d76 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -694,9 +694,11 @@ namespace osu.Game.Screens.Play /// The final score. private async Task prepareScoreForResults() { + var scoreCopy = Score.DeepClone(); + try { - await PrepareScoreForResultsAsync(Score).ConfigureAwait(false); + await PrepareScoreForResultsAsync(scoreCopy).ConfigureAwait(false); } catch (Exception ex) { @@ -705,14 +707,14 @@ namespace osu.Game.Screens.Play try { - await ImportScore(Score).ConfigureAwait(false); + await ImportScore(scoreCopy).ConfigureAwait(false); } catch (Exception ex) { Logger.Error(ex, @"Score import failed!"); } - return Score.ScoreInfo; + return scoreCopy.ScoreInfo; } /// diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 7d15fcf49f..d90e8e0168 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -38,15 +38,13 @@ namespace osu.Game.Screens.Play protected override APIRequest CreateSubmissionRequest(Score score, long token) { - var scoreCopy = score.DeepClone(); - - var beatmap = scoreCopy.ScoreInfo.Beatmap; + var beatmap = score.ScoreInfo.Beatmap; Debug.Assert(beatmap.OnlineBeatmapID != null); int beatmapId = beatmap.OnlineBeatmapID.Value; - return new SubmitSoloScoreRequest(beatmapId, token, scoreCopy.ScoreInfo); + return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); } } } diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 76e9f28dae..65622a4702 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Play { var exiting = base.OnExiting(next); - submitScore(Score); + submitScore(Score.DeepClone()); return exiting; } From b3f60c82535fe292cd172b291449cb0d6a61a878 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jul 2021 19:28:35 +0900 Subject: [PATCH 1598/1621] Fix date being updated on replays unexpectedly --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 1 - osu.Game/Screens/Play/Player.cs | 8 +------- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 ++ 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index eeb881cd39..6a2601170c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -338,7 +338,6 @@ namespace osu.Game.Rulesets.Scoring score.MaxCombo = HighestCombo.Value; score.Accuracy = Accuracy.Value; score.Rank = Rank.Value; - score.Date = DateTimeOffset.Now; foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.IsScorable())) score.Statistics[result] = GetStatistic(result); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2328390d76..dbe8d530b9 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1019,13 +1019,7 @@ namespace osu.Game.Screens.Play /// /// 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) - { - // perform one final population to ensure everything is up-to-date. - ScoreProcessor.PopulateScore(score.ScoreInfo); - - return Task.CompletedTask; - } + protected virtual Task PrepareScoreForResultsAsync(Score score) => Task.CompletedTask; /// /// Creates the for a . diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 65622a4702..5faa384d03 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -109,6 +109,8 @@ namespace osu.Game.Screens.Play { await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); + score.ScoreInfo.Date = DateTimeOffset.Now; + await submitScore(score).ConfigureAwait(false); } From 063f14da98b0491a874b9014c230f5e96e830b07 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 19:55:08 +0900 Subject: [PATCH 1599/1621] Update test room manager to not return passwords --- osu.Game/Online/Rooms/JoinRoomRequest.cs | 12 ++--- .../Multiplayer/TestMultiplayerRoomManager.cs | 47 ++++++++++++------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/osu.Game/Online/Rooms/JoinRoomRequest.cs b/osu.Game/Online/Rooms/JoinRoomRequest.cs index 53bd2a6106..0111882d9a 100644 --- a/osu.Game/Online/Rooms/JoinRoomRequest.cs +++ b/osu.Game/Online/Rooms/JoinRoomRequest.cs @@ -9,23 +9,23 @@ namespace osu.Game.Online.Rooms { public class JoinRoomRequest : APIRequest { - private readonly Room room; - private readonly string password; + public readonly Room Room; + public readonly string Password; public JoinRoomRequest(Room room, string password) { - this.room = room; - this.password = password; + Room = room; + Password = password; } protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); req.Method = HttpMethod.Put; - req.AddParameter("password", password); + req.AddParameter("password", Password); return req; } - protected override string Target => $"rooms/{room.RoomID.Value}/users/{User.Id}"; + protected override string Target => $"rooms/{Room.RoomID.Value}/users/{User.Id}"; } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 5d66cdba02..77238f47fb 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -45,21 +45,33 @@ namespace osu.Game.Tests.Visual.Multiplayer switch (req) { case CreateRoomRequest createRoomRequest: - var createdRoom = new APICreatedRoom(); + var apiRoom = new Room(); - createdRoom.CopyFrom(createRoomRequest.Room); - createdRoom.RoomID.Value ??= currentRoomId++; + apiRoom.CopyFrom(createRoomRequest.Room); + apiRoom.RoomID.Value ??= currentRoomId++; + for (int i = 0; i < apiRoom.Playlist.Count; i++) + apiRoom.Playlist[i].ID = currentPlaylistItemId++; - for (int i = 0; i < createdRoom.Playlist.Count; i++) - createdRoom.Playlist[i].ID = currentPlaylistItemId++; + var responseRoom = new APICreatedRoom(); + responseRoom.CopyFrom(createResponseRoom(apiRoom, false)); - Rooms.Add(createdRoom); - createRoomRequest.TriggerSuccess(createdRoom); + Rooms.Add(apiRoom); + createRoomRequest.TriggerSuccess(responseRoom); return true; case JoinRoomRequest joinRoomRequest: + { + var room = Rooms.Single(r => r.RoomID.Value == joinRoomRequest.Room.RoomID.Value); + + if (joinRoomRequest.Password != room.Password.Value) + { + joinRoomRequest.TriggerFailure(new InvalidOperationException("Invalid password.")); + return true; + } + joinRoomRequest.TriggerSuccess(); return true; + } case PartRoomRequest partRoomRequest: partRoomRequest.TriggerSuccess(); @@ -69,20 +81,13 @@ namespace osu.Game.Tests.Visual.Multiplayer var roomsWithoutParticipants = new List(); foreach (var r in Rooms) - { - var newRoom = new Room(); - - newRoom.CopyFrom(r); - newRoom.RecentParticipants.Clear(); - - roomsWithoutParticipants.Add(newRoom); - } + roomsWithoutParticipants.Add(createResponseRoom(r, false)); getRoomsRequest.TriggerSuccess(roomsWithoutParticipants); return true; case GetRoomRequest getRoomRequest: - getRoomRequest.TriggerSuccess(Rooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId)); + getRoomRequest.TriggerSuccess(createResponseRoom(Rooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true)); return true; case GetBeatmapSetRequest getBeatmapSetRequest: @@ -118,6 +123,16 @@ namespace osu.Game.Tests.Visual.Multiplayer }; } + private Room createResponseRoom(Room room, bool withParticipants) + { + var responseRoom = new Room(); + responseRoom.CopyFrom(room); + responseRoom.Password.Value = null; + if (!withParticipants) + responseRoom.RecentParticipants.Clear(); + return responseRoom; + } + public new void ClearRooms() => base.ClearRooms(); public new void Schedule(Action action) => base.Schedule(action); From 8c0daa89a0ec0d027c0ba20d6d131a94073d5259 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 20:01:44 +0900 Subject: [PATCH 1600/1621] Make test multiplayer client validate password --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index adc632a2b1..82cb68b452 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -119,6 +119,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { var apiRoom = roomManager.Rooms.Single(r => r.RoomID.Value == roomId); + if (password != apiRoom.Password.Value) + throw new InvalidOperationException("Invalid password."); + var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value From 2515785f933682593721279519855552507085a2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 20:02:14 +0900 Subject: [PATCH 1601/1621] Use room password to fill settings textbox --- osu.Game/Online/Rooms/Room.cs | 2 +- .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 1 + osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index f79a410bd9..48e9f94dd5 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -85,6 +85,7 @@ namespace osu.Game.Online.Rooms #region Properties only used for room creation request + [Cached(Name = nameof(Password))] [JsonProperty("password")] public readonly Bindable Password = new Bindable(); @@ -159,7 +160,6 @@ namespace osu.Game.Online.Rooms ChannelId.Value = other.ChannelId.Value; Status.Value = other.Status.Value; Availability.Value = other.Availability.Value; - Password.Value = other.Password.Value; HasPassword.Value = other.HasPassword.Value; Type.Value = other.Type.Value; MaxParticipants.Value = other.MaxParticipants.Value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index fceb124e0a..338d2c9e84 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -271,6 +271,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); RoomID.BindValueChanged(roomId => initialBeatmapControl.Alpha = roomId.NewValue == null ? 1 : 0, true); + Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); operationInProgress.BindTo(ongoingOperationTracker.InProgress); operationInProgress.BindValueChanged(v => diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index eb0b23f13f..0b28bc1a7e 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -56,6 +56,9 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Availability { get; private set; } + [Resolved(typeof(Room), nameof(Room.Password))] + public Bindable Password { get; private set; } + [Resolved(typeof(Room))] protected Bindable Duration { get; private set; } From 26d0eea4854d836e7af67617b7c2688f4d3d7b2a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 20:03:00 +0900 Subject: [PATCH 1602/1621] Set HasPassword correctly in the response room --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 77238f47fb..c94c0b8683 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -127,6 +127,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { var responseRoom = new Room(); responseRoom.CopyFrom(room); + responseRoom.HasPassword.Value = !string.IsNullOrEmpty(responseRoom.Password.Value); responseRoom.Password.Value = null; if (!withParticipants) responseRoom.RecentParticipants.Clear(); From a5a0f12e199f4fc2b7fe171f0879b4974e8744f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 20:07:56 +0900 Subject: [PATCH 1603/1621] Also copy password in test room manager --- .../Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index c94c0b8683..59679f3d66 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -49,6 +49,11 @@ namespace osu.Game.Tests.Visual.Multiplayer apiRoom.CopyFrom(createRoomRequest.Room); apiRoom.RoomID.Value ??= currentRoomId++; + + // Passwords are explicitly not copied between rooms. + apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value); + apiRoom.Password.Value = createRoomRequest.Room.Password.Value; + for (int i = 0; i < apiRoom.Playlist.Count; i++) apiRoom.Playlist[i].ID = currentPlaylistItemId++; @@ -127,7 +132,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { var responseRoom = new Room(); responseRoom.CopyFrom(room); - responseRoom.HasPassword.Value = !string.IsNullOrEmpty(responseRoom.Password.Value); responseRoom.Password.Value = null; if (!withParticipants) responseRoom.RecentParticipants.Clear(); From 1b9d297911e071b569e64f31428a377384c41353 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 20:08:29 +0900 Subject: [PATCH 1604/1621] Add test --- .../Multiplayer/TestSceneMultiplayer.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 7673efb78f..f45ecca424 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -85,6 +85,26 @@ namespace osu.Game.Tests.Visual.Multiplayer // used to test the flow of multiplayer from visual tests. } + [Test] + public void TestCreateRoomWithPassword() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Password = { Value = "password" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + AddAssert("room has password", () => client.APIRoom?.Password.Value == "password"); + } + [Test] public void TestUserSetToIdleWhenBeatmapDeleted() { From d6aa15e5d7c92df054e29a12b6ff2d00068bec8b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 20:19:23 +0900 Subject: [PATCH 1605/1621] Remove local APIRoom from test multiplayer client --- .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 82cb68b452..1528ed0bc8 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public override IBindable IsConnected => isConnected; private readonly Bindable isConnected = new Bindable(true); - public Room? APIRoom { get; private set; } + public new Room? APIRoom => base.APIRoom; public Action? RoomSetupAction; @@ -138,6 +138,7 @@ namespace osu.Game.Tests.Visual.Multiplayer RequiredMods = apiRoom.Playlist.Last().RequiredMods.Select(m => new APIMod(m)).ToArray(), AllowedMods = apiRoom.Playlist.Last().AllowedMods.Select(m => new APIMod(m)).ToArray(), PlaylistItemId = apiRoom.Playlist.Last().ID, + // ReSharper disable once ConstantNullCoalescingCondition Incorrect inspection due to lack of nullable in Room.cs. Password = password ?? string.Empty, }, Users = { localUser }, @@ -147,16 +148,10 @@ namespace osu.Game.Tests.Visual.Multiplayer RoomSetupAction?.Invoke(room); RoomSetupAction = null; - APIRoom = apiRoom; - return Task.FromResult(room); } - protected override Task LeaveRoomInternal() - { - APIRoom = null; - return Task.CompletedTask; - } + protected override Task LeaveRoomInternal() => Task.CompletedTask; public override Task TransferHost(int userId) => ((IMultiplayerClient)this).HostChanged(userId); From 2eec524f2744e68705f9ede888ca57796b2bae4d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 20:20:08 +0900 Subject: [PATCH 1606/1621] Fix password not copied from multiplayer client --- .../Multiplayer/TestSceneMultiplayer.cs | 21 ++++++++++++++ .../Online/Multiplayer/MultiplayerClient.cs | 29 ++++++++++--------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index f45ecca424..4ea628cb55 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -105,6 +105,27 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("room has password", () => client.APIRoom?.Password.Value == "password"); } + [Test] + public void TestLocalPasswordUpdatedWhenMultiplayerSettingsChange() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Password = { Value = "password" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + AddStep("change password", () => client.ChangeSettings(password: "password2")); + AddUntilStep("local password changed", () => client.APIRoom?.Password.Value == "password2"); + } + [Test] public void TestUserSetToIdleWhenBeatmapDeleted() { diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 42d436ef11..9972d7e88d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -92,7 +92,7 @@ namespace osu.Game.Online.Multiplayer [Resolved] private UserLookupCache userLookupCache { get; set; } = null!; - private Room? apiRoom; + protected Room? APIRoom { get; private set; } [BackgroundDependencyLoader] private void load() @@ -139,7 +139,7 @@ namespace osu.Game.Online.Multiplayer await scheduleAsync(() => { Room = joinedRoom; - apiRoom = room; + APIRoom = room; foreach (var user in joinedRoom.Users) updateUserPlayingState(user.UserID, user.State); }, cancellationSource.Token).ConfigureAwait(false); @@ -168,7 +168,7 @@ namespace osu.Game.Online.Multiplayer // For example, if a room was left and the user immediately pressed the "create room" button, then the user could be taken into the lobby if the value of Room is not reset in time. var scheduledReset = scheduleAsync(() => { - apiRoom = null; + APIRoom = null; Room = null; CurrentMatchPlayingUserIds.Clear(); @@ -305,22 +305,22 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return; - Debug.Assert(apiRoom != null); + Debug.Assert(APIRoom != null); Room.State = state; switch (state) { case MultiplayerRoomState.Open: - apiRoom.Status.Value = new RoomStatusOpen(); + APIRoom.Status.Value = new RoomStatusOpen(); break; case MultiplayerRoomState.Playing: - apiRoom.Status.Value = new RoomStatusPlaying(); + APIRoom.Status.Value = new RoomStatusPlaying(); break; case MultiplayerRoomState.Closed: - apiRoom.Status.Value = new RoomStatusEnded(); + APIRoom.Status.Value = new RoomStatusEnded(); break; } @@ -381,12 +381,12 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return; - Debug.Assert(apiRoom != null); + Debug.Assert(APIRoom != null); var user = Room.Users.FirstOrDefault(u => u.UserID == userId); Room.Host = user; - apiRoom.Host.Value = user?.User; + APIRoom.Host.Value = user?.User; RoomUpdated?.Invoke(); }, false); @@ -529,11 +529,12 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return; - Debug.Assert(apiRoom != null); + Debug.Assert(APIRoom != null); // Update a few properties of the room instantaneously. Room.Settings = settings; - apiRoom.Name.Value = Room.Settings.Name; + APIRoom.Name.Value = Room.Settings.Name; + APIRoom.Password.Value = Room.Settings.Password; // The current item update is delayed until an online beatmap lookup (below) succeeds. // In-order for the client to not display an outdated beatmap, the current item is forcefully cleared here. @@ -555,7 +556,7 @@ namespace osu.Game.Online.Multiplayer if (Room == null || !Room.Settings.Equals(settings)) return; - Debug.Assert(apiRoom != null); + Debug.Assert(APIRoom != null); var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineBeatmapID == settings.BeatmapID); beatmap.MD5Hash = settings.BeatmapChecksum; @@ -565,7 +566,7 @@ namespace osu.Game.Online.Multiplayer var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); // Try to retrieve the existing playlist item from the API room. - var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId); + var playlistItem = APIRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId); if (playlistItem != null) updateItem(playlistItem); @@ -573,7 +574,7 @@ namespace osu.Game.Online.Multiplayer { // An existing playlist item does not exist, so append a new one. updateItem(playlistItem = new PlaylistItem()); - apiRoom.Playlist.Add(playlistItem); + APIRoom.Playlist.Add(playlistItem); } CurrentMatchPlayingItem.Value = playlistItem; From 3168a927dc691e6025355cc872fba67c577a3a2c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 20:50:30 +0900 Subject: [PATCH 1607/1621] Fix possible exception --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 561fa220c8..f35671a761 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -326,7 +326,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { // the room may not be left immediately after a disconnection due to async flow, // so checking the IsConnected status is also required. - if (client.Room == null || !client.IsConnected.Value) + if (client?.Room == null || !client.IsConnected.Value) { // room has not been created yet; exit immediately. return base.OnExiting(next); From 05295241b878dc5ac51d4851765af77295fda6f8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 20:55:14 +0900 Subject: [PATCH 1608/1621] Add room joining tests --- .../Multiplayer/TestSceneMultiplayer.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 4ea628cb55..92f9c5733f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -22,6 +23,7 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge; +using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; @@ -85,6 +87,50 @@ namespace osu.Game.Tests.Visual.Multiplayer // used to test the flow of multiplayer from visual tests. } + [Test] + public void TestCreateRoomWithoutPassword() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + } + + [Test] + public void TestJoinRoomWithoutPassword() + { + AddStep("create room", () => + { + API.Queue(new CreateRoomRequest(new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + })); + }); + + AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria()); + AddStep("select room", () => InputManager.Key(Key.Down)); + AddStep("join room", () => InputManager.Key(Key.Enter)); + + AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddUntilStep("wait for join", () => client.Room != null); + } + [Test] public void TestCreateRoomWithPassword() { @@ -105,6 +151,39 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("room has password", () => client.APIRoom?.Password.Value == "password"); } + [Test] + public void TestJoinRoomWithPassword() + { + AddStep("create room", () => + { + API.Queue(new CreateRoomRequest(new Room + { + Name = { Value = "Test Room" }, + Password = { Value = "password" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + })); + }); + + AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria()); + AddStep("select room", () => InputManager.Key(Key.Down)); + AddStep("join room", () => InputManager.Key(Key.Enter)); + + DrawableRoom.PasswordEntryPopover passwordEntryPopover = null; + AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); + AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password"); + AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().Click()); + + AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddUntilStep("wait for join", () => client.Room != null); + } + [Test] public void TestLocalPasswordUpdatedWhenMultiplayerSettingsChange() { From a001e4aa166675a81c8beacc065284aebc158871 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 20:55:25 +0900 Subject: [PATCH 1609/1621] Fix web request failing if password is null --- osu.Game/Online/Rooms/JoinRoomRequest.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/JoinRoomRequest.cs b/osu.Game/Online/Rooms/JoinRoomRequest.cs index 0111882d9a..eab508ab7d 100644 --- a/osu.Game/Online/Rooms/JoinRoomRequest.cs +++ b/osu.Game/Online/Rooms/JoinRoomRequest.cs @@ -22,7 +22,10 @@ namespace osu.Game.Online.Rooms { var req = base.CreateWebRequest(); req.Method = HttpMethod.Put; - req.AddParameter("password", Password); + + if (!string.IsNullOrEmpty(Password)) + req.AddParameter("password", Password); + return req; } From 0a43e54dfc5d605d45f55cc1327cb95056fdc262 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 21:24:22 +0900 Subject: [PATCH 1610/1621] Fix request failing due to parameters --- osu.Game/Online/Rooms/JoinRoomRequest.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Rooms/JoinRoomRequest.cs b/osu.Game/Online/Rooms/JoinRoomRequest.cs index eab508ab7d..b2d772cac7 100644 --- a/osu.Game/Online/Rooms/JoinRoomRequest.cs +++ b/osu.Game/Online/Rooms/JoinRoomRequest.cs @@ -22,13 +22,10 @@ namespace osu.Game.Online.Rooms { var req = base.CreateWebRequest(); req.Method = HttpMethod.Put; - - if (!string.IsNullOrEmpty(Password)) - req.AddParameter("password", Password); - return req; } - protected override string Target => $"rooms/{Room.RoomID.Value}/users/{User.Id}"; + // Todo: Password needs to be specified here rather than via AddParameter() because this is a PUT request. May be a framework bug. + protected override string Target => $"rooms/{Room.RoomID.Value}/users/{User.Id}?password={Password}"; } } From 892d858d5f4852a65cb8e2af022745a0b12a618d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 22:23:31 +0900 Subject: [PATCH 1611/1621] Fix compile error --- osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 951b00376e..54c01e2c20 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -249,7 +249,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { new OsuMenuItem("Create copy", MenuItemType.Standard, () => { - parentScreen?.OpenNewRoom(Room.CreateCopy()); + parentScreen?.OpenNewRoom(Room.DeepClone()); }) }; From 57a99886d5f3fd2da33b9ce8c620dde142338856 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 22:31:01 +0900 Subject: [PATCH 1612/1621] Fix password icon not disappearing when no password --- .../Visual/Multiplayer/TestSceneRoomStatus.cs | 26 +++++++++++++++++++ .../Lounge/Components/DrawableRoom.cs | 19 +++++++------- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs index cec40635f3..6f1b34b078 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs @@ -2,8 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Lounge.Components; @@ -47,5 +51,27 @@ namespace osu.Game.Tests.Visual.Multiplayer } }; } + + [Test] + public void TestEnableAndDisablePassword() + { + DrawableRoom drawableRoom = null; + Room room = null; + + AddStep("create room", () => Child = drawableRoom = new DrawableRoom(room = new Room + { + Name = { Value = "Room with password" }, + Status = { Value = new RoomStatusOpen() }, + Category = { Value = RoomCategory.Realtime }, + }) { MatchingFilter = true }); + + AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha)); + + AddStep("set password", () => room.Password.Value = "password"); + AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType().Single().Alpha)); + + AddStep("unset password", () => room.Password.Value = string.Empty); + AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha)); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 54c01e2c20..236408851f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -58,8 +58,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components [Resolved(canBeNull: true)] private LoungeSubScreen lounge { get; set; } - private Container content; - public readonly Room Room; private SelectionState state; @@ -105,6 +103,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components public bool FilteringActive { get; set; } + private PasswordProtectedIcon passwordIcon; + + private readonly Bindable hasPassword = new Bindable(); + public DrawableRoom(Room room) { Room = room; @@ -138,7 +140,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(SELECTION_BORDER_WIDTH), - Child = content = new Container + Child = new Container { RelativeSizeAxes = Axes.Both, Masking = true, @@ -214,15 +216,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components }, }, }, + passwordIcon = new PasswordProtectedIcon { Alpha = 0 } }, }, }, }; - - if (Room.HasPassword.Value) - { - content.Add(new PasswordProtectedIcon()); - } } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -241,6 +239,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components this.FadeInFromZero(transition_duration); else Alpha = 0; + + hasPassword.BindTo(Room.HasPassword); + hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true); } public Popover GetPopover() => new PasswordEntryPopover(Room) { JoinRequested = lounge.Join }; @@ -313,7 +314,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } - private class PasswordProtectedIcon : CompositeDrawable + public class PasswordProtectedIcon : CompositeDrawable { [BackgroundDependencyLoader] private void load(OsuColour colours) From 7956f73f629641a178f5b0015cdc43de3d2412e1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 22:31:53 +0900 Subject: [PATCH 1613/1621] Move initial content into step --- .../Visual/Multiplayer/TestSceneRoomStatus.cs | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs index 6f1b34b078..8c4133418c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs @@ -16,40 +16,44 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneRoomStatus : OsuTestScene { - public TestSceneRoomStatus() + [Test] + public void TestMultipleStatuses() { - Child = new FillFlowContainer + AddStep("create rooms", () => { - RelativeSizeAxes = Axes.Both, - Width = 0.5f, - Children = new Drawable[] + Child = new FillFlowContainer { - new DrawableRoom(new Room + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Children = new Drawable[] { - Name = { Value = "Open - ending in 1 day" }, - Status = { Value = new RoomStatusOpen() }, - EndDate = { Value = DateTimeOffset.Now.AddDays(1) } - }) { MatchingFilter = true }, - new DrawableRoom(new Room - { - Name = { Value = "Playing - ending in 1 day" }, - Status = { Value = new RoomStatusPlaying() }, - EndDate = { Value = DateTimeOffset.Now.AddDays(1) } - }) { MatchingFilter = true }, - new DrawableRoom(new Room - { - Name = { Value = "Ended" }, - Status = { Value = new RoomStatusEnded() }, - EndDate = { Value = DateTimeOffset.Now } - }) { MatchingFilter = true }, - new DrawableRoom(new Room - { - Name = { Value = "Open" }, - Status = { Value = new RoomStatusOpen() }, - Category = { Value = RoomCategory.Realtime } - }) { MatchingFilter = true }, - } - }; + new DrawableRoom(new Room + { + Name = { Value = "Open - ending in 1 day" }, + Status = { Value = new RoomStatusOpen() }, + EndDate = { Value = DateTimeOffset.Now.AddDays(1) } + }) { MatchingFilter = true }, + new DrawableRoom(new Room + { + Name = { Value = "Playing - ending in 1 day" }, + Status = { Value = new RoomStatusPlaying() }, + EndDate = { Value = DateTimeOffset.Now.AddDays(1) } + }) { MatchingFilter = true }, + new DrawableRoom(new Room + { + Name = { Value = "Ended" }, + Status = { Value = new RoomStatusEnded() }, + EndDate = { Value = DateTimeOffset.Now } + }) { MatchingFilter = true }, + new DrawableRoom(new Room + { + Name = { Value = "Open" }, + Status = { Value = new RoomStatusOpen() }, + Category = { Value = RoomCategory.Realtime } + }) { MatchingFilter = true }, + } + }; + }); } [Test] From 6a55cb9df0e529a137c6fbd430bffb2676afbe40 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Jul 2021 22:52:05 +0900 Subject: [PATCH 1614/1621] Revert unintended change It's a deeper issue with ScreenStack (see: https://github.com/ppy/osu-framework/issues/4619). --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index f35671a761..561fa220c8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -326,7 +326,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { // the room may not be left immediately after a disconnection due to async flow, // so checking the IsConnected status is also required. - if (client?.Room == null || !client.IsConnected.Value) + if (client.Room == null || !client.IsConnected.Value) { // room has not been created yet; exit immediately. return base.OnExiting(next); From 9ea1f5900a78ca02bdb11efa37fd3a87b6f71889 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Jul 2021 01:05:36 +0900 Subject: [PATCH 1615/1621] Don't consider version suffixes when checking for updates This is just to make life easier for me with deploys for now. --- osu.Game/Updater/SimpleUpdateManager.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index 50572a7867..e0409e34df 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework; @@ -41,11 +42,16 @@ namespace osu.Game.Updater var latest = releases.ResponseObject; - if (latest.TagName != version) + // avoid any discrepancies due to build suffixes for now. + // eventually we will want to support release streams and consider these. + version = version.Split('-').First(); + var latestTagName = latest.TagName.Split('-').First(); + + if (latestTagName != version) { Notifications.Post(new SimpleNotification { - Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n" + Text = $"A newer release of osu! has been found ({version} → {latestTagName}).\n\n" + "Click here to download the new version, which can be installed over the top of your existing installation", Icon = FontAwesome.Solid.Upload, Activated = () => From a387d8df740d28acb4d5f285913537711e2db912 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 20 Jul 2021 10:30:40 +0800 Subject: [PATCH 1616/1621] Use `BeatSyncClock` --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 918b9b1c94..f682860498 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -374,8 +374,7 @@ namespace osu.Game.Rulesets.Osu.Mods int timeSignature = (int)timingPoint.TimeSignature; // play metronome from one measure before the first object. - // TODO: Use BeatSyncClock from https://github.com/ppy/osu/pull/13894. - if (Clock.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature) + if (BeatSyncClock.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature) return; sample.Frequency.Value = beatIndex % timeSignature == 0 ? 1 : 0.5f; From 1c6a13fca79aacd28a269effe010b4332a064264 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 20 Jul 2021 10:31:19 +0800 Subject: [PATCH 1617/1621] Disallow mistimed event firing --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index f682860498..e21d1da009 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -353,6 +353,7 @@ namespace osu.Game.Rulesets.Osu.Mods public TargetBeatContainer(double firstHitTime) { this.firstHitTime = firstHitTime; + AllowMistimedEventFiring = false; Divisor = 1; } From fe414b942f69230c473398b8ee2e199c06449a84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Jul 2021 15:44:32 +0900 Subject: [PATCH 1618/1621] Ensure online play subscreen is loaded before forwarding `OnExiting` Closes https://github.com/ppy/osu-framework/issues/4619 (actually not a framework issue; the framework correctly guards against this scenario, see https://github.com/ppy/osu-framework/blob/4e29504384bb78bbccc0d492de58124f17270eb2/osu.Framework/Screens/ScreenStack.cs#L277). Added the assertions to be very explicit about the nested stack's state at this point. Both of those events can only be triggered if the stack has a loaded screen (as far as I can tell), making this check unnecessary in those cases. --- osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index ceee002c6e..3aef05c26e 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -222,7 +223,9 @@ namespace osu.Game.Screens.OnlinePlay this.FadeIn(250); this.ScaleTo(1, 250, Easing.OutSine); - screenStack.CurrentScreen?.OnResuming(last); + Debug.Assert(screenStack.CurrentScreen != null); + screenStack.CurrentScreen.OnResuming(last); + base.OnResuming(last); UpdatePollingRate(isIdle.Value); @@ -233,14 +236,16 @@ namespace osu.Game.Screens.OnlinePlay this.ScaleTo(1.1f, 250, Easing.InSine); this.FadeOut(250); - screenStack.CurrentScreen?.OnSuspending(next); + Debug.Assert(screenStack.CurrentScreen != null); + screenStack.CurrentScreen.OnSuspending(next); UpdatePollingRate(isIdle.Value); } public override bool OnExiting(IScreen next) { - if (screenStack.CurrentScreen?.OnExiting(next) == true) + var subScreen = screenStack.CurrentScreen as Drawable; + if (subScreen?.IsLoaded == true && screenStack.CurrentScreen.OnExiting(next)) return true; RoomManager.PartRoom(); From 4113eae6ade3cc1d55819d0d19cde3fda4458877 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Jul 2021 16:23:48 +0900 Subject: [PATCH 1619/1621] Add test coverage of fail scenario --- .../Multiplayer/TestSceneMultiplayer.cs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 92f9c5733f..f39455dc85 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -104,6 +104,36 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } + [Test] + public void TestExitMidJoin() + { + Room room = null; + + AddStep("create room", () => + { + room = new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }; + }); + + AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria()); + AddStep("select room", () => InputManager.Key(Key.Down)); + AddStep("join room and immediately exit", () => + { + multiplayerScreen.ChildrenOfType().Single().Open(room); + Schedule(() => Stack.CurrentScreen.Exit()); + }); + } + [Test] public void TestJoinRoomWithoutPassword() { @@ -179,9 +209,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password"); AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().Click()); - - AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - AddUntilStep("wait for join", () => client.Room != null); } [Test] From d87ea741edb57bae885f714ec3e81158ce1e4999 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Jul 2021 16:38:39 +0900 Subject: [PATCH 1620/1621] Fix `OnlinePlayScreen` sub-screens not loaded asynchronously --- .../Screens/OnlinePlay/OnlinePlayScreen.cs | 67 +++++++++++-------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index ceee002c6e..a4bcad5776 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -30,17 +30,19 @@ namespace osu.Game.Screens.OnlinePlay [Cached] public abstract class OnlinePlayScreen : OsuScreen, IHasSubScreenStack { - public override bool CursorVisible => (screenStack.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true; + public override bool CursorVisible => (screenStack?.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true; // this is required due to PlayerLoader eventually being pushed to the main stack // while leases may be taken out by a subscreen. public override bool DisallowExternalBeatmapRulesetChanges => true; - private readonly MultiplayerWaveContainer waves; + private MultiplayerWaveContainer waves; - private readonly OsuButton createButton; - private readonly LoungeSubScreen loungeSubScreen; - private readonly ScreenStack screenStack; + private OsuButton createButton; + + private ScreenStack screenStack; + + private LoungeSubScreen loungeSubScreen; private readonly IBindable isIdle = new BindableBool(); @@ -54,7 +56,7 @@ namespace osu.Game.Screens.OnlinePlay private readonly Bindable currentFilter = new Bindable(new FilterCriteria()); [Cached] - private OngoingOperationTracker ongoingOperationTracker { get; set; } + private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker(); [Resolved(CanBeNull = true)] private MusicController music { get; set; } @@ -65,11 +67,14 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] protected IAPIProvider API { get; private set; } + [Resolved(CanBeNull = true)] + private IdleTracker idleTracker { get; set; } + [Resolved(CanBeNull = true)] private OsuLogo logo { get; set; } - private readonly Drawable header; - private readonly Drawable headerBackground; + private Drawable header; + private Drawable headerBackground; protected OnlinePlayScreen() { @@ -78,6 +83,14 @@ namespace osu.Game.Screens.OnlinePlay RelativeSizeAxes = Axes.Both; Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING }; + RoomManager = CreateRoomManager(); + } + + private readonly IBindable apiState = new Bindable(); + + [BackgroundDependencyLoader] + private void load() + { var backgroundColour = Color4Extensions.FromHex(@"3e3a44"); InternalChild = waves = new MultiplayerWaveContainer @@ -144,27 +157,14 @@ namespace osu.Game.Screens.OnlinePlay }; button.Action = () => OpenNewRoom(); }), - RoomManager = CreateRoomManager(), - ongoingOperationTracker = new OngoingOperationTracker() + RoomManager, + ongoingOperationTracker, } }; - screenStack.ScreenPushed += screenPushed; - screenStack.ScreenExited += screenExited; - - screenStack.Push(loungeSubScreen = CreateLounge()); - } - - private readonly IBindable apiState = new Bindable(); - - [BackgroundDependencyLoader(true)] - private void load(IdleTracker idleTracker) - { - apiState.BindTo(API.State); - apiState.BindValueChanged(onlineStateChanged, true); - - if (idleTracker != null) - isIdle.BindTo(idleTracker.IsIdle); + // a lot of the functionality in this class depends on loungeSubScreen being in a ready to go state. + // as such, we intentionally load this inline so it is ready alongside this screen. + LoadComponent(loungeSubScreen = CreateLounge()); } private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => @@ -179,7 +179,20 @@ namespace osu.Game.Screens.OnlinePlay protected override void LoadComplete() { base.LoadComplete(); - isIdle.BindValueChanged(idle => UpdatePollingRate(idle.NewValue), true); + + screenStack.ScreenPushed += screenPushed; + screenStack.ScreenExited += screenExited; + + screenStack.Push(loungeSubScreen); + + apiState.BindTo(API.State); + apiState.BindValueChanged(onlineStateChanged, true); + + if (idleTracker != null) + { + isIdle.BindTo(idleTracker.IsIdle); + isIdle.BindValueChanged(idle => UpdatePollingRate(idle.NewValue), true); + } } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From 26cc4af87c03fe513f4b5d31e27fb61db7245519 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Jul 2021 16:44:51 +0900 Subject: [PATCH 1621/1621] Revert unintended changes --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index f39455dc85..36dd9c2de3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -209,6 +209,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password"); AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().Click()); + + AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddUntilStep("wait for join", () => client.Room != null); } [Test]