From 76e64f501357db6a95a472c34a5389199c459aae Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 14:22:39 +0300 Subject: [PATCH 001/339] Use manual framed clock for lead-in player test scene --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index b195d2aa74..5a1fc1b1e5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; @@ -107,7 +108,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create player", () => { - Beatmap.Value = CreateWorkingBeatmap(beatmap, storyboard); + Beatmap.Value = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), Audio); LoadScreen(player = new LeadInPlayer()); }); From 30b38345aad33a37419f32528cc1ff831e70699a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:21:29 +0300 Subject: [PATCH 002/339] Add ability to highlight chat lines --- osu.Game/Overlays/Chat/ChatLine.cs | 43 +++++++++++++++++------ osu.Game/Overlays/Chat/DrawableChannel.cs | 22 ++++++++++++ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 87d1b1a3ad..e7b349b313 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -42,7 +42,9 @@ namespace osu.Game.Overlays.Chat protected virtual float TextSize => 20; - private Color4 customUsernameColour; + private Container lineBackground; + + private Color4 usernameColour; private OsuSpriteText timestamp; @@ -78,19 +80,22 @@ namespace osu.Game.Overlays.Chat } } - private bool senderHasBackground => !string.IsNullOrEmpty(message.Sender.Colour); + private bool senderHasColour => !string.IsNullOrEmpty(message.Sender.Colour); + + [Resolved] + private OsuColour colours { get; set; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - customUsernameColour = colours.ChatBlue; - - bool hasBackground = senderHasBackground; + usernameColour = senderHasColour + ? Color4Extensions.FromHex(message.Sender.Colour) + : username_colours[message.Sender.Id % username_colours.Length]; Drawable effectedUsername = username = new OsuSpriteText { Shadow = false, - Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length], + Colour = senderHasColour ? colours.ChatBlue : usernameColour, Truncate = true, EllipsisString = "… :", Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true), @@ -99,7 +104,7 @@ namespace osu.Game.Overlays.Chat MaxWidth = MessagePadding - TimestampPadding }; - if (hasBackground) + if (senderHasColour) { // Background effect effectedUsername = new Container @@ -126,7 +131,7 @@ namespace osu.Game.Overlays.Chat new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex(message.Sender.Colour), + Colour = usernameColour, }, new Container { @@ -141,6 +146,14 @@ namespace osu.Game.Overlays.Chat InternalChildren = new Drawable[] { + lineBackground = new Container + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.0f), + CornerRadius = 2f, + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, new Container { Size = new Vector2(MessagePadding, TextSize), @@ -177,7 +190,7 @@ namespace osu.Game.Overlays.Chat { t.Font = OsuFont.GetFont(italics: true); - if (senderHasBackground) + if (senderHasColour) t.Colour = Color4Extensions.FromHex(message.Sender.Colour); } @@ -200,13 +213,21 @@ namespace osu.Game.Overlays.Chat FinishTransforms(true); } + /// + /// Schedules a message highlight animation. + /// + /// + /// Scheduling is required to ensure the animation doesn't play until the chat line is in view and not scrolled away. + /// + public void ScheduleHighlight() => Schedule(() => lineBackground.FlashColour(usernameColour.Darken(1f).Opacity(0.5f), 1000, Easing.InQuint)); + private void updateMessageContent() { this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint); timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; - username.Text = $@"{message.Sender.Username}" + (senderHasBackground || message.IsAction ? "" : ":"); + username.Text = $@"{message.Sender.Username}" + (senderHasColour || message.IsAction ? "" : ":"); // remove non-existent channels from the link list message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument.ToString()) != true); diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 6220beeb82..cca089873f 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -79,6 +79,28 @@ namespace osu.Game.Overlays.Chat Channel.PendingMessageResolved += pendingMessageResolved; } + /// + /// Highlights a specific message in this drawable channel. + /// + /// The message to highlight. + public void HighlightMessage(Message message) + { + if (IsLoaded) + highlightMessage(message); + else + Schedule(highlightMessage, message); + } + + private void highlightMessage(Message message) + { + var chatLine = chatLines.SingleOrDefault(c => c.Message == message); + if (chatLine == null) + return; + + scroll.ScrollIntoView(chatLine); + chatLine.ScheduleHighlight(); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From cb2133944dfdd6cd212fbbfb8385f4e97fa51ce1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:27:09 +0300 Subject: [PATCH 003/339] Add test coverage for channel message highlighting --- .../Online/TestSceneStandAloneChatDisplay.cs | 147 +++++++++++++----- 1 file changed, 104 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index a21647712d..89ee8b03e9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -9,6 +9,8 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Chat; using osuTK.Input; @@ -107,49 +109,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestManyMessages() { - AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = admin, - Content = "I am a wang!" - })); - - AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = redUser, - Content = "I am team red." - })); - - AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = redUser, - Content = "I plan to win!" - })); - - AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = blueUser, - Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand." - })); - - AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = admin, - Content = "Okay okay, calm down guys. Let's do this!" - })); - - AddStep("message from long username", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = longUsernameUser, - Content = "Hi guys, my new username is lit!" - })); - - AddStep("message with new date", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = longUsernameUser, - Content = "Message from the future!", - Timestamp = DateTimeOffset.Now - })); - + sendRegularMessages(); checkScrolledToBottom(); const int messages_per_call = 10; @@ -182,6 +142,61 @@ namespace osu.Game.Tests.Visual.Online checkScrolledToBottom(); } + [Test] + public void TestMessageHighlighting() + { + Message highlighted = null; + + sendRegularMessages(); + + AddStep("highlight first message", () => + { + highlighted = testChannel.Messages[0]; + chatDisplay.DrawableChannel.HighlightMessage(highlighted); + }); + + AddUntilStep("chat scrolled to first message", () => + { + var line = chatDisplay.ChildrenOfType().Single(c => c.Message == highlighted); + return chatDisplay.ScrollContainer.ScreenSpaceDrawQuad.Contains(line.ScreenSpaceDrawQuad.Centre); + }); + + sendMessage(); + checkNotScrolledToBottom(); + + AddStep("highlight last message", () => + { + highlighted = testChannel.Messages[^1]; + chatDisplay.DrawableChannel.HighlightMessage(highlighted); + }); + + AddUntilStep("chat scrolled to last message", () => + { + var line = chatDisplay.ChildrenOfType().Single(c => c.Message == highlighted); + return chatDisplay.ScrollContainer.ScreenSpaceDrawQuad.Contains(line.ScreenSpaceDrawQuad.Centre); + }); + + sendMessage(); + checkScrolledToBottom(); + + AddRepeatStep("highlight other random messages", () => + { + highlighted = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; + chatDisplay.DrawableChannel.HighlightMessage(highlighted); + }, 10); + } + + [Test] + public void TestMessageHighlightingOnFilledChat() + { + fillChat(); + + AddRepeatStep("highlight random messages", () => + { + chatDisplay.DrawableChannel.HighlightMessage(testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]); + }, 10); + } + /// /// Tests that when a message gets wrapped by the chat display getting contracted while scrolled to bottom, the chat will still keep scrolling down. /// @@ -321,6 +336,52 @@ namespace osu.Game.Tests.Visual.Online })); } + private void sendRegularMessages() + { + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = admin, + Content = "I am a wang!" + })); + + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = redUser, + Content = "I am team red." + })); + + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = redUser, + Content = "I plan to win!" + })); + + AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = blueUser, + Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand." + })); + + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = admin, + Content = "Okay okay, calm down guys. Let's do this!" + })); + + AddStep("message from long username", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = longUsernameUser, + Content = "Hi guys, my new username is lit!" + })); + + AddStep("message with new date", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = longUsernameUser, + Content = "Message from the future!", + Timestamp = DateTimeOffset.Now + })); + } + private void checkScrolledToBottom() => AddUntilStep("is scrolled to bottom", () => chatDisplay.ScrolledToBottom); From f4fa80c1e3bd9ae5056185a087f7ee4d65c071c8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:28:40 +0300 Subject: [PATCH 004/339] Add support to highlight messages in chat overlay --- osu.Game/Overlays/ChatOverlay.cs | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index fde9d28b43..d642bf3bb2 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using JetBrains.Annotations; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -46,6 +47,8 @@ namespace osu.Game.Overlays private Container currentChannelContainer; + private DrawableChannel currentDrawableChannel => currentChannelContainer.SingleOrDefault(); + private readonly List loadedChannels = new List(); private LoadingSpinner loading; @@ -249,6 +252,9 @@ namespace osu.Game.Overlays private Bindable currentChannel; + [CanBeNull] + private Message messagePendingHighlight; + private void currentChannelChanged(ValueChangedEvent e) { if (e.NewValue == null) @@ -290,12 +296,24 @@ namespace osu.Game.Overlays currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); currentChannelContainer.FadeIn(500, Easing.OutQuint); + + if (messagePendingHighlight != null) + { + tryHighlightMessage(messagePendingHighlight); + messagePendingHighlight = null; + } }); } else { currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); + + if (messagePendingHighlight != null) + { + tryHighlightMessage(messagePendingHighlight); + messagePendingHighlight = null; + } } // mark channel as read when channel switched @@ -303,6 +321,29 @@ namespace osu.Game.Overlays channelManager.MarkChannelAsRead(e.NewValue); } + /// + /// Highlights a certain message in the specified channel. + /// + /// The message to highlight. + public void HighlightMessage(Message message) + { + if (currentDrawableChannel?.Channel.Id == message.ChannelId) + tryHighlightMessage(message); + else + { + messagePendingHighlight = message; + currentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); + } + } + + private void tryHighlightMessage(Message message) + { + if (message.ChannelId != currentChannel.Value.Id) + return; + + currentDrawableChannel.HighlightMessage(message); + } + private float startDragChatHeight; private bool isDragging; From 741702549b6d45e8e7abb52c7fbd28b52057148d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:31:03 +0300 Subject: [PATCH 005/339] Add test coverage for chat overlay message highlighting --- .../Visual/Online/TestSceneChatOverlay.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 14f32df653..a91cd53ec1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -412,6 +412,59 @@ namespace osu.Game.Tests.Visual.Online AddAssert("channel left", () => !channelManager.JoinedChannels.Contains(multiplayerChannel)); } + [Test] + public void TestHighlightOnCurrentChannel() + { + Message message = null; + + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Send message in channel 1", () => + { + channel1.AddNewMessages(message = new Message + { + ChannelId = channel1.Id, + Content = "Message to highlight!", + Timestamp = DateTimeOffset.Now, + Sender = new APIUser + { + Id = 2, + Username = "Someone", + } + }); + }); + + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + } + + [Test] + public void TestHighlightOnAnotherChannel() + { + Message message = null; + + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Join channel 2", () => channelManager.JoinChannel(channel2)); + AddStep("Send message in channel 2", () => + { + channel2.AddNewMessages(message = new Message + { + ChannelId = channel2.Id, + Content = "Message to highlight!", + Timestamp = DateTimeOffset.Now, + Sender = new APIUser + { + Id = 2, + Username = "Someone", + } + }); + }); + + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + } + private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From 32d242dd627412a38aff0340ae6ba8039e68d84f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:31:32 +0300 Subject: [PATCH 006/339] Hook up message notifications to chat message highlighting logic --- osu.Game/Online/Chat/MessageNotifier.cs | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 2c99e9f9b9..de3b9f71ad 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -99,7 +99,7 @@ namespace osu.Game.Online.Chat if (checkForPMs(channel, message)) continue; - checkForMentions(channel, message); + checkForMentions(message); } } @@ -114,15 +114,15 @@ namespace osu.Game.Online.Chat if (!notifyOnPrivateMessage.Value || channel.Type != ChannelType.PM) return false; - notifications.Post(new PrivateMessageNotification(message.Sender.Username, channel)); + notifications.Post(new PrivateMessageNotification(message)); return true; } - private void checkForMentions(Channel channel, Message message) + private void checkForMentions(Message message) { if (!notifyOnUsername.Value || !CheckContainsUsername(message.Content, localUser.Value.Username)) return; - notifications.Post(new MentionNotification(message.Sender.Username, channel)); + notifications.Post(new MentionNotification(message)); } /// @@ -138,32 +138,32 @@ namespace osu.Game.Online.Chat public class PrivateMessageNotification : OpenChannelNotification { - public PrivateMessageNotification(string username, Channel channel) - : base(channel) + public PrivateMessageNotification(Message message) + : base(message) { Icon = FontAwesome.Solid.Envelope; - Text = $"You received a private message from '{username}'. Click to read it!"; + Text = $"You received a private message from '{message.Sender.Username}'. Click to read it!"; } } public class MentionNotification : OpenChannelNotification { - public MentionNotification(string username, Channel channel) - : base(channel) + public MentionNotification(Message message) + : base(message) { Icon = FontAwesome.Solid.At; - Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; + Text = $"Your name was mentioned in chat by '{message.Sender.Username}'. Click to find out why!"; } } public abstract class OpenChannelNotification : SimpleNotification { - protected OpenChannelNotification(Channel channel) + protected OpenChannelNotification(Message message) { - this.channel = channel; + this.message = message; } - private readonly Channel channel; + private readonly Message message; public override bool IsImportant => false; @@ -175,8 +175,8 @@ namespace osu.Game.Online.Chat Activated = delegate { notificationOverlay.Hide(); + chatOverlay.HighlightMessage(message); chatOverlay.Show(); - channelManager.CurrentChannel.Value = channel; return true; }; From a02adfdbd4efb69dd44d0ed872da398bb8ba09df Mon Sep 17 00:00:00 2001 From: Siim Pender Date: Mon, 7 Mar 2022 20:36:09 +0200 Subject: [PATCH 007/339] Fix crash on super high bpm kiai sections --- osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs index d49b1713f6..46e0030034 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.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.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; @@ -37,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Child .FadeTo(flash_opacity, EarlyActivationMilliseconds, Easing.OutQuint) .Then() - .FadeOut(timingPoint.BeatLength - fade_length, Easing.OutSine); + .FadeOut(Math.Max(0, timingPoint.BeatLength - fade_length), Easing.OutSine); } } } From 22a2ef42c5e855e95df6970eb3659ac6c43ee0f0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 01:22:47 +0300 Subject: [PATCH 008/339] Check channel ID on message highlight using `currentDrawableChannel` --- 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 d642bf3bb2..39513f7963 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -338,7 +338,7 @@ namespace osu.Game.Overlays private void tryHighlightMessage(Message message) { - if (message.ChannelId != currentChannel.Value.Id) + if (message.ChannelId != currentDrawableChannel.Channel.Id) return; currentDrawableChannel.HighlightMessage(message); From 5764c53c1789acbdf37e129ecd0a490c698c589d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 01:22:58 +0300 Subject: [PATCH 009/339] OpenChannelNotification -> HighlightMessageNotification --- osu.Game/Online/Chat/MessageNotifier.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index de3b9f71ad..90b6d927c7 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -136,7 +136,7 @@ namespace osu.Game.Online.Chat return Regex.IsMatch(message, $@"(^|\W)({fullName}|{underscoreName})($|\W)", RegexOptions.IgnoreCase); } - public class PrivateMessageNotification : OpenChannelNotification + public class PrivateMessageNotification : HighlightMessageNotification { public PrivateMessageNotification(Message message) : base(message) @@ -146,7 +146,7 @@ namespace osu.Game.Online.Chat } } - public class MentionNotification : OpenChannelNotification + public class MentionNotification : HighlightMessageNotification { public MentionNotification(Message message) : base(message) @@ -156,9 +156,9 @@ namespace osu.Game.Online.Chat } } - public abstract class OpenChannelNotification : SimpleNotification + public abstract class HighlightMessageNotification : SimpleNotification { - protected OpenChannelNotification(Message message) + protected HighlightMessageNotification(Message message) { this.message = message; } @@ -168,7 +168,7 @@ namespace osu.Game.Online.Chat public override bool IsImportant => false; [BackgroundDependencyLoader] - private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager) + private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay) { IconBackground.Colour = colours.PurpleDark; From 7f47be4680ee16ca16d6bd59185852623306c971 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 03:19:35 +0300 Subject: [PATCH 010/339] Refactor message highlighting logic to rely on a `Channel` data bindable --- .../Online/TestSceneStandAloneChatDisplay.cs | 8 +- osu.Game/Online/Chat/Channel.cs | 7 ++ osu.Game/Overlays/Chat/DrawableChannel.cs | 95 ++++++++++--------- osu.Game/Overlays/ChatOverlay.cs | 33 +------ 4 files changed, 63 insertions(+), 80 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 89ee8b03e9..c9837be934 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -152,7 +152,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("highlight first message", () => { highlighted = testChannel.Messages[0]; - chatDisplay.DrawableChannel.HighlightMessage(highlighted); + chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; }); AddUntilStep("chat scrolled to first message", () => @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("highlight last message", () => { highlighted = testChannel.Messages[^1]; - chatDisplay.DrawableChannel.HighlightMessage(highlighted); + chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; }); AddUntilStep("chat scrolled to last message", () => @@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.Online AddRepeatStep("highlight other random messages", () => { highlighted = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; - chatDisplay.DrawableChannel.HighlightMessage(highlighted); + chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; }, 10); } @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual.Online AddRepeatStep("highlight random messages", () => { - chatDisplay.DrawableChannel.HighlightMessage(testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]); + chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; }, 10); } diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 9cbb2f37e4..a37d3084f0 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Lists; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Chat; namespace osu.Game.Online.Chat { @@ -89,6 +90,12 @@ namespace osu.Game.Online.Chat /// public Bindable Joined = new Bindable(); + /// + /// Signals if there is a message to highlight. + /// This is automatically cleared by the associated after highlighting. + /// + public Bindable HighlightedMessage = new Bindable(); + [JsonConstructor] public Channel() { diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index cca089873f..6ff7cccd65 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -48,6 +49,8 @@ namespace osu.Game.Overlays.Chat RelativeSizeAxes = Axes.Both; } + private Bindable highlightedMessage; + [BackgroundDependencyLoader] private void load() { @@ -71,34 +74,34 @@ namespace osu.Game.Overlays.Chat } }, }; + } - newMessagesArrived(Channel.Messages); + protected override void LoadComplete() + { + base.LoadComplete(); + + addChatLines(Channel.Messages); Channel.NewMessagesArrived += newMessagesArrived; Channel.MessageRemoved += messageRemoved; Channel.PendingMessageResolved += pendingMessageResolved; - } - /// - /// Highlights a specific message in this drawable channel. - /// - /// The message to highlight. - public void HighlightMessage(Message message) - { - if (IsLoaded) - highlightMessage(message); - else - Schedule(highlightMessage, message); - } + highlightedMessage = Channel.HighlightedMessage.GetBoundCopy(); + highlightedMessage.BindValueChanged(m => + { + if (m.NewValue == null) + return; - private void highlightMessage(Message message) - { - var chatLine = chatLines.SingleOrDefault(c => c.Message == message); - if (chatLine == null) - return; + var chatLine = chatLines.SingleOrDefault(c => c.Message == m.NewValue); - scroll.ScrollIntoView(chatLine); - chatLine.ScheduleHighlight(); + if (chatLine != null) + { + scroll.ScrollIntoView(chatLine); + chatLine.ScheduleHighlight(); + } + + highlightedMessage.Value = null; + }, true); } protected override void Dispose(bool isDisposing) @@ -118,18 +121,39 @@ namespace osu.Game.Overlays.Chat Colour = colours.ChatBlue.Lighten(0.7f), }; - private void newMessagesArrived(IEnumerable newMessages) => Schedule(() => + private void newMessagesArrived(IEnumerable newMessages) => Schedule(addChatLines, newMessages); + + private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => { - if (newMessages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id)) + var found = chatLines.LastOrDefault(c => c.Message == existing); + + if (found != null) + { + Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); + + ChatLineFlow.Remove(found); + found.Message = updated; + ChatLineFlow.Add(found); + } + }); + + private void messageRemoved(Message removed) => Schedule(() => + { + chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + }); + + private void addChatLines(IEnumerable messages) + { + if (messages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id)) { // there is a case (on initial population) that we may receive past messages and need to reorder. // easiest way is to just combine messages and recreate drawables (less worrying about day separators etc.) - newMessages = newMessages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList(); + messages = messages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList(); ChatLineFlow.Clear(); } // Add up to last Channel.MAX_HISTORY messages - var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); + var displayMessages = messages.Skip(Math.Max(0, messages.Count() - Channel.MAX_HISTORY)); Message lastMessage = chatLines.LastOrDefault()?.Message; @@ -168,28 +192,9 @@ namespace osu.Game.Overlays.Chat // due to the scroll adjusts from old messages removal above, a scroll-to-end must be enforced, // to avoid making the container think the user has scrolled back up and unwantedly disable auto-scrolling. - if (newMessages.Any(m => m is LocalMessage)) + if (messages.Any(m => m is LocalMessage)) scroll.ScrollToEnd(); - }); - - private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => - { - var found = chatLines.LastOrDefault(c => c.Message == existing); - - if (found != null) - { - Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); - - ChatLineFlow.Remove(found); - found.Message = updated; - ChatLineFlow.Add(found); - } - }); - - private void messageRemoved(Message removed) => Schedule(() => - { - chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); - }); + } private IEnumerable chatLines => ChatLineFlow.Children.OfType(); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 39513f7963..d5e8b209f0 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; -using JetBrains.Annotations; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -47,8 +46,6 @@ namespace osu.Game.Overlays private Container currentChannelContainer; - private DrawableChannel currentDrawableChannel => currentChannelContainer.SingleOrDefault(); - private readonly List loadedChannels = new List(); private LoadingSpinner loading; @@ -252,9 +249,6 @@ namespace osu.Game.Overlays private Bindable currentChannel; - [CanBeNull] - private Message messagePendingHighlight; - private void currentChannelChanged(ValueChangedEvent e) { if (e.NewValue == null) @@ -296,24 +290,12 @@ namespace osu.Game.Overlays currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); currentChannelContainer.FadeIn(500, Easing.OutQuint); - - if (messagePendingHighlight != null) - { - tryHighlightMessage(messagePendingHighlight); - messagePendingHighlight = null; - } }); } else { currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); - - if (messagePendingHighlight != null) - { - tryHighlightMessage(messagePendingHighlight); - messagePendingHighlight = null; - } } // mark channel as read when channel switched @@ -327,21 +309,10 @@ namespace osu.Game.Overlays /// The message to highlight. public void HighlightMessage(Message message) { - if (currentDrawableChannel?.Channel.Id == message.ChannelId) - tryHighlightMessage(message); - else - { - messagePendingHighlight = message; + if (currentChannel.Value.Id != message.ChannelId) currentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); - } - } - private void tryHighlightMessage(Message message) - { - if (message.ChannelId != currentDrawableChannel.Channel.Id) - return; - - currentDrawableChannel.HighlightMessage(message); + currentChannel.Value.HighlightedMessage.Value = message; } private float startDragChatHeight; From f8e5570e4152b2ac52c859f086a73abb8ec0f3f0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 03:20:20 +0300 Subject: [PATCH 011/339] Fix `Message` equality not passing on equal references --- osu.Game/Online/Chat/Message.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index dcd15f9028..3db63c3fca 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -59,7 +59,13 @@ namespace osu.Game.Online.Chat return Id.Value.CompareTo(other.Id.Value); } - public virtual bool Equals(Message other) => Id.HasValue && Id == other?.Id; + public virtual bool Equals(Message other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Id.HasValue && Id == other?.Id; + } // ReSharper disable once ImpureMethodCallOnReadonlyValueField public override int GetHashCode() => Id.GetHashCode(); From f64586995888b1b2d05a9e586d9dd888107954c1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 03:42:17 +0300 Subject: [PATCH 012/339] Update `ChannelManager.CurrentChannel` directly to handle non-loaded chat scenario `currentChannel` gets instantiated once the chat overlay is open, while `HighlightMessage` could be called while the chat overlay has never been open. This will all be rewritten with the new chat overlay design anyways, so should be fine for now. --- osu.Game/Overlays/ChatOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index d5e8b209f0..d6a1b23c46 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -309,10 +309,10 @@ namespace osu.Game.Overlays /// The message to highlight. public void HighlightMessage(Message message) { - if (currentChannel.Value.Id != message.ChannelId) - currentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); + if (channelManager.CurrentChannel.Value.Id != message.ChannelId) + channelManager.CurrentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); - currentChannel.Value.HighlightedMessage.Value = message; + channelManager.CurrentChannel.Value.HighlightedMessage.Value = message; } private float startDragChatHeight; From d74064b94b93d229e39b4c2ebc6161bb8d5ff000 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 03:56:27 +0300 Subject: [PATCH 013/339] Use `Equals` instead of reference equality operator --- 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 6ff7cccd65..a73e61c52b 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Chat if (m.NewValue == null) return; - var chatLine = chatLines.SingleOrDefault(c => c.Message == m.NewValue); + var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); if (chatLine != null) { From 5e0882df8de12a0229f6d0c39910ebd6ebb1b94b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 04:00:11 +0300 Subject: [PATCH 014/339] Simplify message highlighting transforms --- osu.Game/Overlays/Chat/ChatLine.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index e7b349b313..97fda80492 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Chat protected virtual float TextSize => 20; - private Container lineBackground; + private Container lineHighlightBackground; private Color4 usernameColour; @@ -146,10 +146,11 @@ namespace osu.Game.Overlays.Chat InternalChildren = new Drawable[] { - lineBackground = new Container + lineHighlightBackground = new Container { + Alpha = 0f, RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.0f), + Colour = usernameColour.Darken(1f), CornerRadius = 2f, Masking = true, Child = new Box { RelativeSizeAxes = Axes.Both } @@ -219,7 +220,7 @@ namespace osu.Game.Overlays.Chat /// /// Scheduling is required to ensure the animation doesn't play until the chat line is in view and not scrolled away. /// - public void ScheduleHighlight() => Schedule(() => lineBackground.FlashColour(usernameColour.Darken(1f).Opacity(0.5f), 1000, Easing.InQuint)); + public void ScheduleHighlight() => Schedule(() => lineHighlightBackground.FadeTo(0.5f).FadeOut(1000, Easing.InQuint)); private void updateMessageContent() { From daa42584f43214c8e0939b512bf00d59b785704b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 19:36:08 +0900 Subject: [PATCH 015/339] Fix feedback from realm writes causing offset slider to jump around --- .../Play/PlayerSettings/BeatmapOffsetControl.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index c00b2f56dc..d4babe3e9f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.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 System.Diagnostics; using System.Linq; @@ -23,8 +25,6 @@ using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osuTK; -#nullable enable - namespace osu.Game.Screens.Play.PlayerSettings { public class BeatmapOffsetControl : CompositeDrawable @@ -122,7 +122,16 @@ namespace osu.Game.Screens.Play.PlayerSettings beatmapOffsetSubscription = realm.SubscribeToPropertyChanged( r => r.Find(beatmap.Value.BeatmapInfo.ID)?.UserSettings, settings => settings.Offset, - val => Current.Value = val); + val => + { + // At the point we reach here, it's not guaranteed that all realm writes have taken place (there may be some in-flight). + // We are only aware of writes that originated from our own flow, so if we do see one that's active we can avoid handling the feedback value arriving. + if (realmWriteTask == null) + Current.Value = val; + + // we can also mark any in-flight write that is managed locally as "seen" and start handling any incoming changes again. + realmWriteTask = null; + }); Current.BindValueChanged(currentChanged); } From 960b6528cad6209f20180f84586940ec9d8cc79f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 19:36:23 +0900 Subject: [PATCH 016/339] Ensure the value used during realm async write is the same as whe compared for equality --- .../Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index d4babe3e9f..9e280fdca2 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -167,10 +167,12 @@ namespace osu.Game.Screens.Play.PlayerSettings if (settings == null) // only the case for tests. return; - if (settings.Offset == Current.Value) + double val = Current.Value; + + if (settings.Offset == val) return; - settings.Offset = Current.Value; + settings.Offset = val; }); } } From c867068cae9fbf589327ed4616939b1e91c1a0a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 21:15:14 +0900 Subject: [PATCH 017/339] 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 5b26b8f36e..c2788f9a48 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 d86fbc693e..14cb751958 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 c37692f0d8..e485c69096 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 2c382bd1d9acdad5fa0e9ba4d80f996c36d5044e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 11:26:07 +0900 Subject: [PATCH 018/339] Rename GetImmediateScore() as overload of GetScore() --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 2 +- .../PerformanceBreakdownCalculator.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 46 +++++++++---------- .../HUD/MultiplayerGameplayLeaderboard.cs | 2 +- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index c6e7988543..e96ff1f7f1 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -300,7 +300,7 @@ namespace osu.Game.Tests.Rulesets.Scoring HitObjects = { new TestHitObject(result) } }); - Assert.That(scoreProcessor.GetImmediateScore(ScoringMode.Standardised, result.AffectsCombo() ? 1 : 0, statistic), Is.EqualTo(expectedScore).Within(0.5d)); + Assert.That(scoreProcessor.GetScore(ScoringMode.Standardised, result.AffectsCombo() ? 1 : 0, statistic), Is.EqualTo(expectedScore).Within(0.5d)); } private class TestJudgement : Judgement diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 3d384f5914..dfb8066b28 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Difficulty ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.HighestCombo.Value = perfectPlay.MaxCombo; scoreProcessor.Mods.Value = perfectPlay.Mods; - perfectPlay.TotalScore = (long)scoreProcessor.GetImmediateScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); + perfectPlay.TotalScore = (long)scoreProcessor.GetScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); // compute rank achieved // default to SS, then adjust the rank with mods diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index d5a5aa4592..af9b4db5ca 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -218,6 +218,29 @@ namespace osu.Game.Rulesets.Scoring scoreResultCounts); } + /// + /// Given a minimal set of inputs, return the computed score for the tracked beatmap / mods combination, at the current point in time. + /// + /// The to compute the total score in. + /// The maximum combo achievable in the beatmap. + /// Statistics to be used for calculating accuracy, bonus score, etc. + /// The computed score for provided inputs. + public double GetScore(ScoringMode mode, int maxCombo, Dictionary statistics) + { + // calculate base score from statistics pairs + int computedBaseScore = 0; + + foreach (var pair in statistics) + { + if (!pair.Key.AffectsAccuracy()) + continue; + + computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; + } + + return GetScore(mode, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); + } + /// /// Computes the total score. /// @@ -250,29 +273,6 @@ namespace osu.Game.Rulesets.Scoring } } - /// - /// Given a minimal set of inputs, return the computed score for the tracked beatmap / mods combination, at the current point in time. - /// - /// The to compute the total score in. - /// The maximum combo achievable in the beatmap. - /// Statistics to be used for calculating accuracy, bonus score, etc. - /// The computed score for provided inputs. - public double GetImmediateScore(ScoringMode mode, int maxCombo, Dictionary statistics) - { - // calculate base score from statistics pairs - int computedBaseScore = 0; - - foreach (var pair in statistics) - { - if (!pair.Key.AffectsAccuracy()) - continue; - - computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; - } - - return GetScore(mode, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); - } - /// /// Get the accuracy fraction for the provided base score. /// diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 83c73e5a70..0516e00b8b 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -244,7 +244,7 @@ namespace osu.Game.Screens.Play.HUD { var header = frame.Header; - Score.Value = ScoreProcessor.GetImmediateScore(ScoringMode.Value, header.MaxCombo, header.Statistics); + Score.Value = ScoreProcessor.GetScore(ScoringMode.Value, header.MaxCombo, header.Statistics); Accuracy.Value = header.Accuracy; CurrentCombo.Value = header.Combo; } From 6654977a7b73c19d2250b030e519055c8be7cbae Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 11:28:14 +0900 Subject: [PATCH 019/339] Add GetScore() overload with total hitobject count --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index af9b4db5ca..67b78d5230 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -250,6 +250,17 @@ namespace osu.Game.Rulesets.Scoring /// Any statistics to be factored in. /// The total score. public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics) + { + int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); + + // If there are no hitobjects then the beatmap can be composed of only ticks or spinners, so ensure we don't multiply by 0 at all times. + if (totalHitObjects == 0) + totalHitObjects = 1; + + return GetScore(mode, accuracyRatio, comboRatio, statistics, totalHitObjects); + } + + public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics, int totalHitObjects) { switch (mode) { @@ -260,12 +271,6 @@ namespace osu.Game.Rulesets.Scoring return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; case ScoringMode.Classic: - int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); - - // If there are no hitobjects then the beatmap can be composed of only ticks or spinners, so ensure we don't multiply by 0 at all times. - if (totalHitObjects == 0) - totalHitObjects = 1; - // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; From 5b6b8d1fa95c32f10500b838b95cf116dbe59abf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 11:29:47 +0900 Subject: [PATCH 020/339] Remove GetStandardisedScore() proxy method --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 14 +++----------- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 3 ++- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 67b78d5230..422b29601a 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -207,16 +207,10 @@ namespace osu.Game.Rulesets.Scoring if (rollingMaxBaseScore != 0) Accuracy.Value = calculateAccuracyRatio(baseScore, true); - TotalScore.Value = getScore(Mode.Value); + TotalScore.Value = GetScore(Mode.Value); } - private double getScore(ScoringMode mode) - { - return GetScore(mode, - calculateAccuracyRatio(baseScore), - calculateComboRatio(HighestCombo.Value), - scoreResultCounts); - } + public double GetScore(ScoringMode mode) => GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); /// /// Given a minimal set of inputs, return the computed score for the tracked beatmap / mods combination, at the current point in time. @@ -316,8 +310,6 @@ namespace osu.Game.Rulesets.Scoring public int GetStatistic(HitResult result) => scoreResultCounts.GetValueOrDefault(result); - public double GetStandardisedScore() => getScore(ScoringMode.Standardised); - /// /// Resets this ScoreProcessor to a default state. /// @@ -351,7 +343,7 @@ namespace osu.Game.Rulesets.Scoring /// public virtual void PopulateScore(ScoreInfo score) { - score.TotalScore = (long)Math.Round(GetStandardisedScore()); + score.TotalScore = (long)Math.Round(GetScore(ScoringMode.Standardised)); score.Combo = Combo.Value; score.MaxCombo = HighestCombo.Value; score.Accuracy = Accuracy.Value; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 7efeae8129..77eaf133c7 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -11,6 +11,7 @@ using osu.Framework.Screens; using osu.Game.Extensions; using osu.Game.Online.Rooms; using osu.Game.Rulesets; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; @@ -64,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); - Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore()); + Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetScore(ScoringMode.Standardised)); } protected override void Dispose(bool isDisposing) From a8e99f1a9521bdc528aaf66817183bc5f1e6b085 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 11:45:04 +0900 Subject: [PATCH 021/339] Calculate classic score using total basic hitobject count --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 93 +++++++++++++++------ 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 422b29601a..b0656e270e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; @@ -86,9 +87,22 @@ namespace osu.Game.Rulesets.Scoring /// private double maxBaseScore; + /// + /// The maximum number of basic (non-tick and non-bonus) hitobjects. + /// + private int maxBasicHitObjects; + + /// + /// Maximum for a normal hit (i.e. not tick/bonus) for this ruleset. + /// Only populated via . + /// + private HitResult? maxBasicHitResult; + private double rollingMaxBaseScore; private double baseScore; + private int basicHitObjects; + private readonly Dictionary scoreResultCounts = new Dictionary(); private readonly List hitEvents = new List(); private HitObject lastHitObject; @@ -122,8 +136,6 @@ namespace osu.Game.Rulesets.Scoring }; } - private readonly Dictionary scoreResultCounts = new Dictionary(); - protected sealed override void ApplyResultInternal(JudgementResult result) { result.ComboAtJudgement = Combo.Value; @@ -160,6 +172,9 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore += result.Judgement.MaxNumericResult; } + if (result.Type.IsBasic()) + basicHitObjects++; + hitEvents.Add(CreateHitEvent(result)); lastHitObject = result.HitObject; @@ -195,6 +210,9 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } + if (result.Type.IsBasic()) + basicHitObjects--; + Debug.Assert(hitEvents.Count > 0); lastHitObject = hitEvents[^1].LastHitObject; hitEvents.RemoveAt(hitEvents.Count - 1); @@ -210,15 +228,30 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = GetScore(Mode.Value); } - public double GetScore(ScoringMode mode) => GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); + /// + /// Computes the total score from judgements that have been applied to this + /// through and . + /// + /// + /// Requires an to have been applied via before use. + /// + /// The to represent the score as. + /// The total score in the given . + public double GetScore(ScoringMode mode) + { + return GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); + } /// - /// Given a minimal set of inputs, return the computed score for the tracked beatmap / mods combination, at the current point in time. + /// Computes the total score from judgements counts in a statistics dictionary. /// - /// The to compute the total score in. + /// + /// Requires an to have been applied via before use. + /// + /// The to represent the score as. /// The maximum combo achievable in the beatmap. - /// Statistics to be used for calculating accuracy, bonus score, etc. - /// The computed score for provided inputs. + /// The statistics to compute the score for. + /// The total score computed from judgements in the statistics dictionary. public double GetScore(ScoringMode mode, int maxCombo, Dictionary statistics) { // calculate base score from statistics pairs @@ -236,25 +269,34 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Computes the total score. + /// Computes the total score from given scoring component ratios. /// - /// The to compute the total score in. + /// + /// Requires an to have been applied via before use. + /// + /// The to represent the score as. /// The accuracy percentage achieved by the player. - /// The proportion of the max combo achieved by the player. - /// Any statistics to be factored in. - /// The total score. + /// The portion of the max combo achieved by the player. + /// Any additional statistics to be factored in. + /// The total score computed from the given scoring component ratios. public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics) { - int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); - - // If there are no hitobjects then the beatmap can be composed of only ticks or spinners, so ensure we don't multiply by 0 at all times. - if (totalHitObjects == 0) - totalHitObjects = 1; - - return GetScore(mode, accuracyRatio, comboRatio, statistics, totalHitObjects); + return GetScore(mode, accuracyRatio, comboRatio, maxBasicHitObjects, statistics); } - public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics, int totalHitObjects) + /// + /// Computes the total score from given scoring component ratios. + /// + /// + /// Does not require an to have been applied via before use. + /// + /// The to represent the score as. + /// The accuracy percentage achieved by the player. + /// The portion of the max combo achieved by the player. + /// Any additional statistics to be factored in. + /// The total number of basic (non-tick and non-bonus) hitobjects in the beatmap. + /// The total score computed from the given scoring component ratios. + public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, int totalBasicHitObjects, Dictionary statistics) { switch (mode) { @@ -268,7 +310,7 @@ namespace osu.Game.Rulesets.Scoring // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; - return Math.Pow(scaledStandardised * totalHitObjects, 2) * 36; + return Math.Pow(scaledStandardised * totalBasicHitObjects, 2) * 36; } } @@ -326,10 +368,12 @@ namespace osu.Game.Rulesets.Scoring { maxAchievableCombo = HighestCombo.Value; maxBaseScore = baseScore; + maxBasicHitObjects = basicHitObjects; } baseScore = 0; rollingMaxBaseScore = 0; + basicHitObjects = 0; TotalScore.Value = 0; Accuracy.Value = 1; @@ -355,11 +399,6 @@ namespace osu.Game.Rulesets.Scoring score.HitEvents = hitEvents; } - /// - /// Maximum for a normal hit (i.e. not tick/bonus) for this ruleset. Only populated via . - /// - private HitResult? maxNormalResult; - public override void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) { base.ResetFromReplayFrame(ruleset, frame); @@ -394,7 +433,7 @@ namespace osu.Game.Rulesets.Scoring break; default: - maxResult = maxNormalResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; + maxResult = maxBasicHitResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; break; } From f1c40bd9eda6dfda1bcd6669c548b8e078c8396f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 12:57:59 +0900 Subject: [PATCH 022/339] Rework GetScore() method signatures + implementations Rename legacy-facing overload to mention as much --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 25 ++- .../Visual/Gameplay/TestSceneFailJudgement.cs | 2 +- .../TestSceneMultiSpectatorLeaderboard.cs | 5 +- ...TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- ...ceneMultiplayerGameplayLeaderboardTeams.cs | 2 +- .../PerformanceBreakdownCalculator.cs | 3 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 190 ++++++++++-------- osu.Game/Scoring/ScoreManager.cs | 15 +- .../Multiplayer/MultiplayerPlayer.cs | 2 +- .../Spectate/MultiSpectatorLeaderboard.cs | 11 +- .../Spectate/MultiSpectatorScreen.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 2 +- .../HUD/MultiplayerGameplayLeaderboard.cs | 25 ++- 13 files changed, 173 insertions(+), 113 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index e96ff1f7f1..d327f4844e 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -6,11 +6,15 @@ using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; 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.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Rulesets.Scoring @@ -300,7 +304,26 @@ namespace osu.Game.Tests.Rulesets.Scoring HitObjects = { new TestHitObject(result) } }); - Assert.That(scoreProcessor.GetScore(ScoringMode.Standardised, result.AffectsCombo() ? 1 : 0, statistic), Is.EqualTo(expectedScore).Within(0.5d)); + Assert.That(scoreProcessor.ComputeFinalScore(ScoringMode.Standardised, new ScoreInfo + { + Ruleset = new TestRuleset().RulesetInfo, + MaxCombo = result.AffectsCombo() ? 1 : 0, + Statistics = statistic + }), Is.EqualTo(expectedScore).Within(0.5d)); + } + + private class TestRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) => throw new System.NotImplementedException(); + + 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(IWorkingBeatmap beatmap) => throw new System.NotImplementedException(); + + public override string Description { get; } + public override string ShortName { get; } } private class TestJudgement : Judgement diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index 6430c29dfa..79d7bb366d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits > 1); AddAssert("total number of results == 1", () => { - var score = new ScoreInfo(); + var score = new ScoreInfo { Ruleset = Ruleset.Value }; ((FailPlayer)Player).ScoreProcessor.PopulateScore(score); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 42bb99de24..f57a54d84c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -46,7 +46,10 @@ namespace osu.Game.Tests.Visual.Multiplayer var scoreProcessor = new OsuScoreProcessor(); scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) { Expanded = { Value = true } }, Add); + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) + { + Expanded = { Value = true } + }, Add); }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index cfac5da4ff..bcd4474876 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Multiplayer scoreProcessor.ApplyBeatmap(playableBeatmap); - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray()) + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(Ruleset.Value, scoreProcessor, multiplayerUsers.ToArray()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index f751b162d1..7f5aced925 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer scoreProcessor.ApplyBeatmap(playableBeatmap); - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray()) + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(Ruleset.Value, scoreProcessor, multiplayerUsers.ToArray()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index dfb8066b28..fc38ed0298 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -63,9 +63,8 @@ namespace osu.Game.Rulesets.Difficulty // calculate total score ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); - scoreProcessor.HighestCombo.Value = perfectPlay.MaxCombo; scoreProcessor.Mods.Value = perfectPlay.Mods; - perfectPlay.TotalScore = (long)scoreProcessor.GetScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); + perfectPlay.TotalScore = (long)scoreProcessor.ComputeFinalScore(ScoringMode.Standardised, perfectPlay); // compute rank achieved // default to SS, then adjust the rank with mods diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b0656e270e..3004464df7 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Utils; -using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; @@ -93,10 +92,10 @@ namespace osu.Game.Rulesets.Scoring private int maxBasicHitObjects; /// - /// Maximum for a normal hit (i.e. not tick/bonus) for this ruleset. - /// Only populated via . + /// The maximum of a basic (non-tick and non-bonus) hitobject. + /// Only populated via or . /// - private HitResult? maxBasicHitResult; + private HitResult? maxBasicResult; private double rollingMaxBaseScore; private double baseScore; @@ -222,81 +221,110 @@ namespace osu.Game.Rulesets.Scoring private void updateScore() { - if (rollingMaxBaseScore != 0) - Accuracy.Value = calculateAccuracyRatio(baseScore, true); + double rollingAccuracyRatio = rollingMaxBaseScore > 0 ? baseScore / rollingMaxBaseScore : 1; + double accuracyRatio = maxBaseScore > 0 ? baseScore / maxBaseScore : 1; + double comboRatio = maxAchievableCombo > 0 ? (double)HighestCombo.Value / maxAchievableCombo : 1; - TotalScore.Value = GetScore(Mode.Value); + Accuracy.Value = rollingAccuracyRatio; + TotalScore.Value = ComputeScore(Mode.Value, accuracyRatio, comboRatio, getBonusScore(scoreResultCounts), maxBasicHitObjects); } /// - /// Computes the total score from judgements that have been applied to this - /// through and . + /// Computes the total score of a given finalised . This should be used when a score is known to be complete. /// /// - /// Requires an to have been applied via before use. + /// Does not require to have been called before use. /// /// The to represent the score as. + /// The to compute the total score of. /// The total score in the given . - public double GetScore(ScoringMode mode) + public double ComputeFinalScore(ScoringMode mode, ScoreInfo scoreInfo) { - return GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); + extractFromStatistics(scoreInfo.Ruleset.CreateInstance(), + scoreInfo.Statistics, + out double extractedBaseScore, + out double extractedMaxBaseScore, + out int extractedMaxCombo, + out int extractedBasicHitObjects); + + double accuracyRatio = extractedMaxBaseScore > 0 ? extractedBaseScore / extractedMaxBaseScore : 1; + double comboRatio = extractedMaxCombo > 0 ? (double)scoreInfo.MaxCombo / extractedMaxCombo : 1; + + return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), extractedBasicHitObjects); } /// - /// Computes the total score from judgements counts in a statistics dictionary. + /// Computes the total score of a partially-completed . This should be used when it is unknown whether a score is complete. /// /// - /// Requires an to have been applied via before use. + /// Requires to have been called before use. /// /// The to represent the score as. - /// The maximum combo achievable in the beatmap. - /// The statistics to compute the score for. - /// The total score computed from judgements in the statistics dictionary. - public double GetScore(ScoringMode mode, int maxCombo, Dictionary statistics) + /// The to compute the total score of. + /// The total score in the given . + public double ComputePartialScore(ScoringMode mode, ScoreInfo scoreInfo) { - // calculate base score from statistics pairs - int computedBaseScore = 0; + extractFromStatistics(scoreInfo.Ruleset.CreateInstance(), + scoreInfo.Statistics, + out double extractedBaseScore, + out _, + out _, + out _); - foreach (var pair in statistics) + double accuracyRatio = maxBaseScore > 0 ? extractedBaseScore / maxBaseScore : 1; + double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1; + + return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), maxBasicHitObjects); + } + + /// + /// Computes the total score of a given with a given custom max achievable combo. + /// + /// + /// This is useful for processing legacy scores in which the maximum achievable combo can be more accurately determined via external means (e.g. database values or difficulty calculation). + ///

Does not require to have been called before use.

+ ///
+ /// The to represent the score as. + /// The to compute the total score of. + /// The maximum achievable combo for the provided beatmap. + /// The total score in the given . + public double ComputeFinalLegacyScore(ScoringMode mode, ScoreInfo scoreInfo, int maxAchievableCombo) + { + double accuracyRatio = scoreInfo.Accuracy; + double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1; + + // For legacy osu!mania scores, a full-GREAT score has 100% accuracy. If combined with a full-combo, the score becomes indistinguishable from a full-PERFECT score. + // To get around this, the accuracy ratio is always recalculated based on the hit statistics rather than trusting the score. + // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. + if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3) { - if (!pair.Key.AffectsAccuracy()) - continue; + extractFromStatistics( + scoreInfo.Ruleset.CreateInstance(), + scoreInfo.Statistics, + out double computedBaseScore, + out double computedMaxBaseScore, + out _, + out _); - computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; + if (computedMaxBaseScore > 0) + accuracyRatio = computedBaseScore / computedMaxBaseScore; } - return GetScore(mode, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); + int computedBasicHitObjects = scoreInfo.Statistics.Where(kvp => kvp.Key.IsBasic()).Select(kvp => kvp.Value).Sum(); + + return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), computedBasicHitObjects); } /// - /// Computes the total score from given scoring component ratios. + /// Computes the total score from individual scoring components. /// - /// - /// Requires an to have been applied via before use. - /// /// The to represent the score as. /// The accuracy percentage achieved by the player. /// The portion of the max combo achieved by the player. - /// Any additional statistics to be factored in. - /// The total score computed from the given scoring component ratios. - public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics) - { - return GetScore(mode, accuracyRatio, comboRatio, maxBasicHitObjects, statistics); - } - - /// - /// Computes the total score from given scoring component ratios. - /// - /// - /// Does not require an to have been applied via before use. - /// - /// The to represent the score as. - /// The accuracy percentage achieved by the player. - /// The portion of the max combo achieved by the player. - /// Any additional statistics to be factored in. + /// The total bonus score. /// The total number of basic (non-tick and non-bonus) hitobjects in the beatmap. /// The total score computed from the given scoring component ratios. - public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, int totalBasicHitObjects, Dictionary statistics) + public double ComputeScore(ScoringMode mode, double accuracyRatio, double comboRatio, double bonusScore, int totalBasicHitObjects) { switch (mode) { @@ -304,33 +332,22 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Standardised: double accuracyScore = accuracyPortion * accuracyRatio; double comboScore = comboPortion * comboRatio; - return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; + return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier; case ScoringMode.Classic: // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. - double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; - return Math.Pow(scaledStandardised * totalBasicHitObjects, 2) * 36; + double scaledStandardised = ComputeScore(ScoringMode.Standardised, accuracyRatio, comboRatio, bonusScore, totalBasicHitObjects) / max_score; + return Math.Pow(scaledStandardised * Math.Max(1, totalBasicHitObjects), 2) * 36; } } /// - /// Get the accuracy fraction for the provided base score. + /// Calculates the total bonus score from score statistics. /// - /// The score to be used for accuracy calculation. - /// Whether the rolling base score should be used (ie. for the current point in time based on Apply/Reverted results). - /// The computed accuracy. - private double calculateAccuracyRatio(double baseScore, bool preferRolling = false) - { - if (preferRolling && rollingMaxBaseScore != 0) - return baseScore / rollingMaxBaseScore; - - return maxBaseScore > 0 ? baseScore / maxBaseScore : 1; - } - - private double calculateComboRatio(int maxCombo) => maxAchievableCombo > 0 ? (double)maxCombo / maxAchievableCombo : 1; - - private double getBonusScore(Dictionary statistics) + /// The score statistics. + /// The total bonus score. + private double getBonusScore(IReadOnlyDictionary statistics) => statistics.GetValueOrDefault(HitResult.SmallBonus) * Judgement.SMALL_BONUS_SCORE + statistics.GetValueOrDefault(HitResult.LargeBonus) * Judgement.LARGE_BONUS_SCORE; @@ -387,16 +404,17 @@ namespace osu.Game.Rulesets.Scoring /// public virtual void PopulateScore(ScoreInfo score) { - score.TotalScore = (long)Math.Round(GetScore(ScoringMode.Standardised)); score.Combo = Combo.Value; score.MaxCombo = HighestCombo.Value; score.Accuracy = Accuracy.Value; score.Rank = Rank.Value; + score.HitEvents = hitEvents; foreach (var result in HitResultExtensions.ALL_TYPES) score.Statistics[result] = GetStatistic(result); - score.HitEvents = hitEvents; + // Populate total score after everything else. + score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score)); } public override void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) @@ -406,11 +424,26 @@ namespace osu.Game.Rulesets.Scoring if (frame.Header == null) return; - baseScore = 0; - rollingMaxBaseScore = 0; + extractFromStatistics(ruleset, frame.Header.Statistics, out baseScore, out rollingMaxBaseScore, out _, out _); HighestCombo.Value = frame.Header.MaxCombo; - foreach ((HitResult result, int count) in frame.Header.Statistics) + scoreResultCounts.Clear(); + scoreResultCounts.AddRange(frame.Header.Statistics); + + updateScore(); + + OnResetFromReplayFrame?.Invoke(); + } + + private void extractFromStatistics(Ruleset ruleset, IReadOnlyDictionary statistics, out double baseScore, out double maxBaseScore, out int maxCombo, + out int basicHitObjects) + { + baseScore = 0; + maxBaseScore = 0; + maxCombo = 0; + basicHitObjects = 0; + + foreach ((HitResult result, int count) in statistics) { // Bonus scores are counted separately directly from the statistics dictionary later on. if (!result.IsScorable() || result.IsBonus()) @@ -433,20 +466,19 @@ namespace osu.Game.Rulesets.Scoring break; default: - maxResult = maxBasicHitResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; + maxResult = maxBasicResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; break; } baseScore += count * Judgement.ToNumericResult(result); - rollingMaxBaseScore += count * Judgement.ToNumericResult(maxResult); + maxBaseScore += count * Judgement.ToNumericResult(maxResult); + + if (result.AffectsCombo()) + maxCombo += count; + + if (result.IsBasic()) + basicHitObjects += count; } - - scoreResultCounts.Clear(); - scoreResultCounts.AddRange(frame.Header.Statistics); - - updateScore(); - - OnResetFromReplayFrame?.Invoke(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 963c4a77ca..6e49e3c9af 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -18,7 +18,6 @@ using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; namespace osu.Game.Scoring @@ -136,21 +135,9 @@ namespace osu.Game.Scoring return score.TotalScore; int beatmapMaxCombo; - double accuracy = score.Accuracy; if (score.IsLegacyScore) { - if (score.RulesetID == 3) - { - // In osu!stable, a full-GREAT score has 100% accuracy in mania. Along with a full combo, the score becomes indistinguishable from a full-PERFECT score. - // To get around this, recalculate accuracy based on the hit statistics. - // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. - double maxBaseScore = score.Statistics.Select(kvp => kvp.Value).Sum() * Judgement.ToNumericResult(HitResult.Perfect); - double baseScore = score.Statistics.Select(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value).Sum(); - if (maxBaseScore > 0) - accuracy = baseScore / maxBaseScore; - } - // This score is guaranteed to be an osu!stable score. // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used. if (score.BeatmapInfo.MaxCombo != null) @@ -184,7 +171,7 @@ namespace osu.Game.Scoring var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.Mods.Value = score.Mods; - return (long)Math.Round(scoreProcessor.GetScore(mode, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); + return (long)Math.Round(scoreProcessor.ComputeFinalLegacyScore(mode, score, beatmapMaxCombo)); } /// diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index bd2f49a9e5..d8fb448437 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }); // todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area. - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, users), l => + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(GameplayState.Ruleset.RulesetInfo, ScoreProcessor, users), l => { if (!LoadedBeatmapSuccessfully) return; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs index 1614828a78..4545913db8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs @@ -5,6 +5,7 @@ using System; using JetBrains.Annotations; using osu.Framework.Timing; using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -12,8 +13,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard { - public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users) - : base(scoreProcessor, users) + public MultiSpectatorLeaderboard(RulesetInfo ruleset, [NotNull] ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users) + : base(ruleset, scoreProcessor, users) { } @@ -33,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate ((SpectatingTrackedUserData)data).Clock = null; } - protected override TrackedUserData CreateUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(user, scoreProcessor); + protected override TrackedUserData CreateUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(user, ruleset, scoreProcessor); protected override void Update() { @@ -48,8 +49,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [CanBeNull] public IClock Clock; - public SpectatingTrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) - : base(user, scoreProcessor) + public SpectatingTrackedUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) + : base(user, ruleset, scoreProcessor) { } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 3bb76c4a76..6747b8fc66 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -133,7 +133,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor(); scoreProcessor.ApplyBeatmap(playableBeatmap); - LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, users) + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, users) { Expanded = { Value = true }, }, l => diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 77eaf133c7..5a7762a3d8 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); - Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetScore(ScoringMode.Standardised)); + Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.ComputeFinalScore(ScoringMode.Standardised, Score.ScoreInfo)); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 0516e00b8b..71998622ef 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -17,7 +17,9 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Spectator; +using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD @@ -41,6 +43,7 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private UserLookupCache userLookupCache { get; set; } + private readonly RulesetInfo ruleset; private readonly ScoreProcessor scoreProcessor; private readonly MultiplayerRoomUser[] playingUsers; private Bindable scoringMode; @@ -52,11 +55,13 @@ namespace osu.Game.Screens.Play.HUD /// /// Construct a new leaderboard. /// + /// The ruleset. /// A score processor instance to handle score calculation for scores of users in the match. /// IDs of all users in this match. - public MultiplayerGameplayLeaderboard(ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users) + public MultiplayerGameplayLeaderboard(RulesetInfo ruleset, ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users) { // todo: this will eventually need to be created per user to support different mod combinations. + this.ruleset = ruleset; this.scoreProcessor = scoreProcessor; playingUsers = users; @@ -69,7 +74,7 @@ namespace osu.Game.Screens.Play.HUD foreach (var user in playingUsers) { - var trackedUser = CreateUserData(user, scoreProcessor); + var trackedUser = CreateUserData(user, ruleset, scoreProcessor); trackedUser.ScoringMode.BindTo(scoringMode); UserScores[user.UserID] = trackedUser; @@ -119,7 +124,7 @@ namespace osu.Game.Screens.Play.HUD spectatorClient.OnNewFrames += handleIncomingFrames; } - protected virtual TrackedUserData CreateUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) => new TrackedUserData(user, scoreProcessor); + protected virtual TrackedUserData CreateUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) => new TrackedUserData(user, ruleset, scoreProcessor); protected override GameplayLeaderboardScore CreateLeaderboardScoreDrawable(APIUser user, bool isTracked) { @@ -222,8 +227,12 @@ namespace osu.Game.Screens.Play.HUD public int? Team => (User.MatchState as TeamVersusUserState)?.TeamID; - public TrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) + private readonly RulesetInfo ruleset; + + public TrackedUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) { + this.ruleset = ruleset; + User = user; ScoreProcessor = scoreProcessor; @@ -244,7 +253,13 @@ namespace osu.Game.Screens.Play.HUD { var header = frame.Header; - Score.Value = ScoreProcessor.GetScore(ScoringMode.Value, header.MaxCombo, header.Statistics); + Score.Value = ScoreProcessor.ComputePartialScore(ScoringMode.Value, new ScoreInfo + { + Ruleset = ruleset, + MaxCombo = header.MaxCombo, + Statistics = header.Statistics + }); + Accuracy.Value = header.Accuracy; CurrentCombo.Value = header.Combo; } From 6fd8b4d8918f9095c9651cf88047c5467c10b06e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 22:14:58 +0900 Subject: [PATCH 023/339] Safeguard method against invalid invocation --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 3004464df7..5e11d4da72 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; @@ -100,6 +101,7 @@ namespace osu.Game.Rulesets.Scoring private double rollingMaxBaseScore; private double baseScore; private int basicHitObjects; + private bool beatmapApplied; private readonly Dictionary scoreResultCounts = new Dictionary(); private readonly List hitEvents = new List(); @@ -135,6 +137,12 @@ namespace osu.Game.Rulesets.Scoring }; } + public override void ApplyBeatmap(IBeatmap beatmap) + { + base.ApplyBeatmap(beatmap); + beatmapApplied = true; + } + protected sealed override void ApplyResultInternal(JudgementResult result) { result.ComboAtJudgement = Combo.Value; @@ -264,6 +272,9 @@ namespace osu.Game.Rulesets.Scoring /// The total score in the given . public double ComputePartialScore(ScoringMode mode, ScoreInfo scoreInfo) { + if (!beatmapApplied) + throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}."); + extractFromStatistics(scoreInfo.Ruleset.CreateInstance(), scoreInfo.Statistics, out double extractedBaseScore, From 512536f5fe3465b329c1ccb6f7a2a7e23d8d6a65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 23:25:51 +0900 Subject: [PATCH 024/339] Fix unconditional null in `Equals` implementation --- osu.Game/Online/Chat/Message.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 3db63c3fca..ad004e2881 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -64,7 +64,7 @@ namespace osu.Game.Online.Chat if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Id.HasValue && Id == other?.Id; + return Id.HasValue && Id == other.Id; } // ReSharper disable once ImpureMethodCallOnReadonlyValueField From e91226f5786e1d481c7bffcbe778a77056e6eed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Mar 2022 21:41:10 +0100 Subject: [PATCH 025/339] Fix 'auto-property never assigned to' inspections --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index d327f4844e..2a19d51c9d 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -322,8 +322,8 @@ namespace osu.Game.Tests.Rulesets.Scoring public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new System.NotImplementedException(); - public override string Description { get; } - public override string ShortName { get; } + public override string Description => string.Empty; + public override string ShortName => string.Empty; } private class TestJudgement : Judgement From e2001148d570ca139df57cf7cbd606adf8f50363 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 8 Mar 2022 21:47:54 +0000 Subject: [PATCH 026/339] Implement strict tracking mod --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 3 + .../Mods/OsuModStrictTracking.cs | 59 +++++++++++++++++++ .../Objects/SliderTailCircle.cs | 4 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index e04a30d06c..f46573c494 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.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.Bindables; using osu.Game.Configuration; @@ -16,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset { + public override Type[] IncompatibleMods => new[] { typeof(OsuModStrictTracking) }; + [SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")] public Bindable NoSliderHeadAccuracy { get; } = new BindableBool(true); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs new file mode 100644 index 0000000000..13da422049 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.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; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +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.Mods +{ + public class OsuModStrictTracking : Mod, IApplicableToDifficulty, IApplicableToDrawableHitObject, IApplicableToHitObject + { + public override string Name => @"Strict Tracking"; + public override string Acronym => @"ST"; + public override IconUsage? Icon => FontAwesome.Solid.PenFancy; + public override ModType Type => ModType.DifficultyIncrease; + public override string Description => @"Follow circles just got serious..."; + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => new[] { typeof(ModClassic) }; + + public void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + difficulty.SliderTickRate = 0.0; + } + + public void ApplyToDrawableHitObject(DrawableHitObject drawable) + { + if (drawable is DrawableSlider slider) + { + slider.Tracking.ValueChanged += e => + { + if (e.NewValue || slider.Judged) return; + + slider.MissForcefully(); + + foreach (var o in slider.NestedHitObjects) + { + if (o is DrawableOsuHitObject h && !o.Judged) + h.MissForcefully(); + } + }; + } + } + + public void ApplyToHitObject(HitObject hitObject) + { + switch (hitObject) + { + case Slider slider: + slider.TailCircle.JudgeAsSliderTick = true; + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index f9450062f4..70459dc432 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -14,12 +14,14 @@ namespace osu.Game.Rulesets.Osu.Objects /// public class SliderTailCircle : SliderEndCircle { + public bool JudgeAsSliderTick = false; + public SliderTailCircle(Slider slider) : base(slider) { } - public override Judgement CreateJudgement() => new SliderTailJudgement(); + public override Judgement CreateJudgement() => JudgeAsSliderTick ? (OsuJudgement)new SliderTickJudgement() : new SliderTailJudgement(); public class SliderTailJudgement : OsuJudgement { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 5ade164566..faaab1a8e2 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -159,6 +159,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), + new OsuModStrictTracking() }; case ModType.Conversion: From a3477c3841d4c85f81a5fe5546e695c8ebbd836c Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 8 Mar 2022 22:30:58 +0000 Subject: [PATCH 027/339] Implement `ChannelListing` for new chat design Adds components `ChannelListing` and `ChannelListing` item with visual test. Essentially a more simplified version of the existing `ChannelSelectionOverlay` component. Correctly implements `IFilterable` behaviour to filter child channel items. Channel joined state is based on the underlying `Joined` bindable of the `Channel` class. Channel join/leave events are exposed via `OnRequestJoin` and `OnRequestLeave` events which should be handled by parent component. Requires a cached `OverlayColourScheme` instance to be provided by the parent overlay component when added. --- .../Online/NewChat/TestSceneChannelListing.cs | 90 +++++++++ osu.Game/Overlays/NewChat/ChannelListing.cs | 85 +++++++++ .../Overlays/NewChat/ChannelListingItem.cs | 175 ++++++++++++++++++ 3 files changed, 350 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs create mode 100644 osu.Game/Overlays/NewChat/ChannelListing.cs create mode 100644 osu.Game/Overlays/NewChat/ChannelListingItem.cs diff --git a/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs b/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs new file mode 100644 index 0000000000..cbec23a305 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osu.Game.Overlays.NewChat; +using osuTK; + +namespace osu.Game.Tests.Visual.Online.NewChat +{ + [TestFixture] + public class TestSceneChannelListing : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider overlayColours = new OverlayColourProvider(OverlayColourScheme.Pink); + + private SearchTextBox search; + private ChannelListing listing; + + [SetUp] + public void SetUp() + { + Schedule(() => + { + Children = new Drawable[] + { + search = new SearchTextBox + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 300, + Margin = new MarginPadding { Top = 100 }, + }, + listing = new ChannelListing + { + Size = new Vector2(800, 400), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }; + listing.Show(); + search.Current.ValueChanged += term => listing.SearchTerm = term.NewValue; + }); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Add Join/Leave callbacks", () => + { + listing.OnRequestJoin += channel => channel.Joined.Value = true; + listing.OnRequestLeave += channel => channel.Joined.Value = false; + }); + } + + [Test] + public void TestAddRandomChannels() + { + AddStep("Add Random Channels", () => + { + listing.UpdateAvailableChannels(createRandomChannels(20)); + }); + } + + private Channel createRandomChannel() + { + var id = RNG.Next(0, 10000); + return new Channel + { + Name = $"#channel-{id}", + Topic = RNG.Next(4) < 3 ? $"We talk about the number {id} here" : null, + Type = ChannelType.Public, + Id = id, + }; + } + + private List createRandomChannels(int num) + => Enumerable.Range(0, num) + .Select(_ => createRandomChannel()) + .ToList(); + } +} diff --git a/osu.Game/Overlays/NewChat/ChannelListing.cs b/osu.Game/Overlays/NewChat/ChannelListing.cs new file mode 100644 index 0000000000..79371d0fed --- /dev/null +++ b/osu.Game/Overlays/NewChat/ChannelListing.cs @@ -0,0 +1,85 @@ +// 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 System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Chat; +using osuTK; + +namespace osu.Game.Overlays.NewChat +{ + public class ChannelListing : VisibilityContainer + { + public event Action? OnRequestJoin; + public event Action? OnRequestLeave; + + public string SearchTerm + { + get => flow.SearchTerm; + set => flow.SearchTerm = value; + } + + private SearchContainer flow = null!; + + [Resolved] + private OverlayColourProvider overlayColours { get; set; } = null!; + + public ChannelListing() + { + Masking = true; + } + + protected override void PopIn() => this.FadeIn(); + protected override void PopOut() => this.FadeOut(); + + public void UpdateAvailableChannels(IEnumerable newChannels) + { + flow.ChildrenEnumerable = newChannels.Where(c => c.Type == ChannelType.Public) + .Select(c => new ChannelListingItem(c)); + + foreach (var item in flow.Children) + { + item.OnRequestJoin += channel => OnRequestJoin?.Invoke(channel); + item.OnRequestLeave += channel => OnRequestLeave?.Invoke(channel); + } + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = overlayColours.Background4, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarAnchor = Anchor.TopRight, + Child = flow = new SearchContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(3), + Padding = new MarginPadding + { + Vertical = 13, + Horizontal = 15, + }, + }, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/NewChat/ChannelListingItem.cs b/osu.Game/Overlays/NewChat/ChannelListingItem.cs new file mode 100644 index 0000000000..845db4e802 --- /dev/null +++ b/osu.Game/Overlays/NewChat/ChannelListingItem.cs @@ -0,0 +1,175 @@ +// 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 System.Collections.Generic; +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.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; +using osuTK; + +namespace osu.Game.Overlays.NewChat +{ + public class ChannelListingItem : OsuClickableContainer, IFilterable + { + public event Action? OnRequestJoin; + public event Action? OnRequestLeave; + + public bool FilteringActive { get; set; } + public IEnumerable FilterTerms => new[] { channel.Name, channel.Topic ?? string.Empty }; + public bool MatchingFilter + { + set => this.FadeTo(value ? 1f : 0f, 100); + } + + private readonly float TEXT_SIZE = 18; + private readonly float ICON_SIZE = 14; + private readonly Channel channel; + + private Colour4 selectedColour; + private Colour4 normalColour; + + private Box hoverBox = null!; + private SpriteIcon checkbox = null!; + private OsuSpriteText channelText = null!; + private IBindable channelJoined = null!; + + [Resolved] + private OverlayColourProvider overlayColours { get; set; } = null!; + + public ChannelListingItem(Channel channel) + { + this.channel = channel; + + Masking = true; + CornerRadius = 5; + RelativeSizeAxes = Axes.X; + Height = 20; + } + + protected override bool OnHover(HoverEvent e) + { + hoverBox.Show(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoverBox.Hide(); + base.OnHoverLost(e); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Set colours + normalColour = overlayColours.Light3; + selectedColour = Colour4.White; + + // Set handlers for state display + channelJoined = channel.Joined.GetBoundCopy(); + channelJoined.BindValueChanged(change => + { + if (change.NewValue) + { + checkbox.Show(); + channelText.Colour = selectedColour; + } + else + { + checkbox.Hide(); + channelText.Colour = normalColour; + } + }, true); + + // Set action on click + Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + hoverBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = overlayColours.Background3, + Alpha = 0f, + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new Dimension[] + { + new Dimension(GridSizeMode.Absolute, 40), + new Dimension(GridSizeMode.Absolute, 200), + new Dimension(GridSizeMode.Absolute, 400), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }, + Content = new[] + { + new Drawable[] + { + checkbox = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 15 }, + Icon = FontAwesome.Solid.Check, + Size = new Vector2(ICON_SIZE), + }, + channelText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = $"# {channel.Name.Substring(1)}", + Font = OsuFont.Torus.With(size: TEXT_SIZE, weight: FontWeight.Medium), + Margin = new MarginPadding { Bottom = 2 }, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = channel.Topic, + Font = OsuFont.Torus.With(size: TEXT_SIZE), + Margin = new MarginPadding { Bottom = 2 }, + Colour = Colour4.White, + }, + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.User, + Size = new Vector2(ICON_SIZE), + Margin = new MarginPadding { Right = 5 }, + Colour = overlayColours.Light3, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = "0", + Font = OsuFont.Numeric.With(size: TEXT_SIZE, weight: FontWeight.Medium), + Margin = new MarginPadding { Bottom = 2 }, + Colour = overlayColours.Light3, + }, + }, + }, + }, + }; + } + } +} From 7dd51a9c4a69ab6b6ef2618bc25c4eec08689002 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 9 Mar 2022 00:12:44 +0000 Subject: [PATCH 028/339] Reorder class attributes --- osu.Game/Overlays/NewChat/ChannelListing.cs | 40 +++---- .../Overlays/NewChat/ChannelListingItem.cs | 110 +++++++++--------- 2 files changed, 72 insertions(+), 78 deletions(-) diff --git a/osu.Game/Overlays/NewChat/ChannelListing.cs b/osu.Game/Overlays/NewChat/ChannelListing.cs index 79371d0fed..3de6364303 100644 --- a/osu.Game/Overlays/NewChat/ChannelListing.cs +++ b/osu.Game/Overlays/NewChat/ChannelListing.cs @@ -30,27 +30,7 @@ namespace osu.Game.Overlays.NewChat private SearchContainer flow = null!; [Resolved] - private OverlayColourProvider overlayColours { get; set; } = null!; - - public ChannelListing() - { - Masking = true; - } - - protected override void PopIn() => this.FadeIn(); - protected override void PopOut() => this.FadeOut(); - - public void UpdateAvailableChannels(IEnumerable newChannels) - { - flow.ChildrenEnumerable = newChannels.Where(c => c.Type == ChannelType.Public) - .Select(c => new ChannelListingItem(c)); - - foreach (var item in flow.Children) - { - item.OnRequestJoin += channel => OnRequestJoin?.Invoke(channel); - item.OnRequestLeave += channel => OnRequestLeave?.Invoke(channel); - } - } + private OverlayColourProvider colourProvider { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -60,7 +40,7 @@ namespace osu.Game.Overlays.NewChat new Box { RelativeSizeAxes = Axes.Both, - Colour = overlayColours.Background4, + Colour = colourProvider.Background4, }, new OsuScrollContainer { @@ -81,5 +61,21 @@ namespace osu.Game.Overlays.NewChat }, }; } + + public void UpdateAvailableChannels(IEnumerable newChannels) + { + flow.ChildrenEnumerable = newChannels.Where(c => c.Type == ChannelType.Public) + .Select(c => new ChannelListingItem(c)); + + foreach (var item in flow.Children) + { + item.OnRequestJoin += channel => OnRequestJoin?.Invoke(channel); + item.OnRequestLeave += channel => OnRequestLeave?.Invoke(channel); + } + } + + protected override void PopIn() => this.FadeIn(); + + protected override void PopOut() => this.FadeOut(); } } diff --git a/osu.Game/Overlays/NewChat/ChannelListingItem.cs b/osu.Game/Overlays/NewChat/ChannelListingItem.cs index 845db4e802..7e7cf36e91 100644 --- a/osu.Game/Overlays/NewChat/ChannelListingItem.cs +++ b/osu.Game/Overlays/NewChat/ChannelListingItem.cs @@ -27,15 +27,13 @@ namespace osu.Game.Overlays.NewChat public bool FilteringActive { get; set; } public IEnumerable FilterTerms => new[] { channel.Name, channel.Topic ?? string.Empty }; - public bool MatchingFilter - { - set => this.FadeTo(value ? 1f : 0f, 100); - } + public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); } - private readonly float TEXT_SIZE = 18; - private readonly float ICON_SIZE = 14; private readonly Channel channel; + private const float TEXT_SIZE = 18; + private const float ICON_SIZE = 14; + private Colour4 selectedColour; private Colour4 normalColour; @@ -45,73 +43,33 @@ namespace osu.Game.Overlays.NewChat private IBindable channelJoined = null!; [Resolved] - private OverlayColourProvider overlayColours { get; set; } = null!; + private OverlayColourProvider colourProvider { get; set; } = null!; public ChannelListingItem(Channel channel) { this.channel = channel; - - Masking = true; - CornerRadius = 5; - RelativeSizeAxes = Axes.X; - Height = 20; - } - - protected override bool OnHover(HoverEvent e) - { - hoverBox.Show(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hoverBox.Hide(); - base.OnHoverLost(e); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - // Set colours - normalColour = overlayColours.Light3; - selectedColour = Colour4.White; - - // Set handlers for state display - channelJoined = channel.Joined.GetBoundCopy(); - channelJoined.BindValueChanged(change => - { - if (change.NewValue) - { - checkbox.Show(); - channelText.Colour = selectedColour; - } - else - { - checkbox.Hide(); - channelText.Colour = normalColour; - } - }, true); - - // Set action on click - Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); } [BackgroundDependencyLoader] private void load() { + Masking = true; + CornerRadius = 5; + RelativeSizeAxes = Axes.X; + Height = 20; + Children = new Drawable[] { hoverBox = new Box { RelativeSizeAxes = Axes.Both, - Colour = overlayColours.Background3, + Colour = colourProvider.Background3, Alpha = 0f, }, new GridContainer { RelativeSizeAxes = Axes.Both, - ColumnDimensions = new Dimension[] + ColumnDimensions = new[] { new Dimension(GridSizeMode.Absolute, 40), new Dimension(GridSizeMode.Absolute, 200), @@ -155,7 +113,7 @@ namespace osu.Game.Overlays.NewChat Icon = FontAwesome.Solid.User, Size = new Vector2(ICON_SIZE), Margin = new MarginPadding { Right = 5 }, - Colour = overlayColours.Light3, + Colour = colourProvider.Light3, }, new OsuSpriteText { @@ -164,12 +122,52 @@ namespace osu.Game.Overlays.NewChat Text = "0", Font = OsuFont.Numeric.With(size: TEXT_SIZE, weight: FontWeight.Medium), Margin = new MarginPadding { Bottom = 2 }, - Colour = overlayColours.Light3, + Colour = colourProvider.Light3, }, }, }, }, }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Set colours + normalColour = colourProvider.Light3; + selectedColour = Colour4.White; + + // Set handlers for state display + channelJoined = channel.Joined.GetBoundCopy(); + channelJoined.BindValueChanged(change => + { + if (change.NewValue) + { + checkbox.Show(); + channelText.Colour = selectedColour; + } + else + { + checkbox.Hide(); + channelText.Colour = normalColour; + } + }, true); + + // Set action on click + Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); + } + + protected override bool OnHover(HoverEvent e) + { + hoverBox.Show(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoverBox.Hide(); + base.OnHoverLost(e); + } } } From 7daa2d0ea4dd67f4aea73274ee2db9198f8b1871 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 9 Mar 2022 00:33:46 +0000 Subject: [PATCH 029/339] Use correct fonts and colours in ChannelListingItem --- .../Overlays/NewChat/ChannelListingItem.cs | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/NewChat/ChannelListingItem.cs b/osu.Game/Overlays/NewChat/ChannelListingItem.cs index 7e7cf36e91..77d5599c9b 100644 --- a/osu.Game/Overlays/NewChat/ChannelListingItem.cs +++ b/osu.Game/Overlays/NewChat/ChannelListingItem.cs @@ -31,20 +31,18 @@ namespace osu.Game.Overlays.NewChat private readonly Channel channel; - private const float TEXT_SIZE = 18; - private const float ICON_SIZE = 14; - - private Colour4 selectedColour; - private Colour4 normalColour; - private Box hoverBox = null!; private SpriteIcon checkbox = null!; private OsuSpriteText channelText = null!; + private OsuSpriteText topicText = null!; private IBindable channelJoined = null!; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + private const float text_size = 18; + private const float icon_size = 14; + public ChannelListingItem(Channel channel) { this.channel = channel; @@ -87,31 +85,30 @@ namespace osu.Game.Overlays.NewChat Origin = Anchor.CentreLeft, Margin = new MarginPadding { Left = 15 }, Icon = FontAwesome.Solid.Check, - Size = new Vector2(ICON_SIZE), + Size = new Vector2(icon_size), }, channelText = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = $"# {channel.Name.Substring(1)}", - Font = OsuFont.Torus.With(size: TEXT_SIZE, weight: FontWeight.Medium), + Text = channel.Name, + Font = OsuFont.Torus.With(size: text_size, weight: FontWeight.SemiBold), Margin = new MarginPadding { Bottom = 2 }, }, - new OsuSpriteText + topicText = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Text = channel.Topic, - Font = OsuFont.Torus.With(size: TEXT_SIZE), + Font = OsuFont.Torus.With(size: text_size), Margin = new MarginPadding { Bottom = 2 }, - Colour = Colour4.White, }, new SpriteIcon { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Icon = FontAwesome.Solid.User, - Size = new Vector2(ICON_SIZE), + Size = new Vector2(icon_size), Margin = new MarginPadding { Right = 5 }, Colour = colourProvider.Light3, }, @@ -120,7 +117,7 @@ namespace osu.Game.Overlays.NewChat Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Text = "0", - Font = OsuFont.Numeric.With(size: TEXT_SIZE, weight: FontWeight.Medium), + Font = OsuFont.Torus.With(size: text_size), Margin = new MarginPadding { Bottom = 2 }, Colour = colourProvider.Light3, }, @@ -134,27 +131,23 @@ namespace osu.Game.Overlays.NewChat { base.LoadComplete(); - // Set colours - normalColour = colourProvider.Light3; - selectedColour = Colour4.White; - - // Set handlers for state display channelJoined = channel.Joined.GetBoundCopy(); channelJoined.BindValueChanged(change => { if (change.NewValue) { checkbox.Show(); - channelText.Colour = selectedColour; + channelText.Colour = Colour4.White; + topicText.Colour = Colour4.White; } else { checkbox.Hide(); - channelText.Colour = normalColour; + channelText.Colour = colourProvider.Light3; + topicText.Colour = colourProvider.Content2; } }, true); - // Set action on click Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); } From 9720a06b16105f23fc9f151e67d3af0cce63004d Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 9 Mar 2022 00:52:33 +0000 Subject: [PATCH 030/339] Use `int` in ChannelListing test --- osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs b/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs index cbec23a305..8f955c0520 100644 --- a/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs +++ b/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Online.NewChat private Channel createRandomChannel() { - var id = RNG.Next(0, 10000); + int id = RNG.Next(0, 10000); return new Channel { Name = $"#channel-{id}", From 6bf436cd62d0c844d74964837fb2387ebe112725 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 13:52:58 +0900 Subject: [PATCH 031/339] Only null the realm write task if it actually completed --- .../Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 9e280fdca2..42091c521f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -129,8 +129,11 @@ namespace osu.Game.Screens.Play.PlayerSettings if (realmWriteTask == null) Current.Value = val; - // we can also mark any in-flight write that is managed locally as "seen" and start handling any incoming changes again. - realmWriteTask = null; + if (realmWriteTask?.IsCompleted == true) + { + // we can also mark any in-flight write that is managed locally as "seen" and start handling any incoming changes again. + realmWriteTask = null; + } }); Current.BindValueChanged(currentChanged); From b07a1e8d09b50bd8511dabcbc75c49a55439687c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Mar 2022 15:38:00 +0900 Subject: [PATCH 032/339] Fix unable to copy playlist rooms without first opening --- osu.Game/Online/Rooms/Room.cs | 19 +-------- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 39 +++++++++++++++++++ 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index a33150fe08..543b176b51 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -10,12 +10,11 @@ using osu.Game.IO.Serialization.Converters; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms.RoomStatuses; -using osu.Game.Utils; namespace osu.Game.Online.Rooms { [JsonObject(MemberSerialization.OptIn)] - public class Room : IDeepCloneable + public class Room { [Cached] [JsonProperty("id")] @@ -153,22 +152,6 @@ namespace osu.Game.Online.Rooms 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. - /// - public Room DeepClone() - { - var copy = new Room(); - - copy.CopyFrom(this); - - // ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not. - copy.RoomID.Value = null; - - return copy; - } - public void CopyFrom(Room other) { RoomID.Value = other.RoomID.Value; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 27743e709f..7baa346c6f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { new OsuMenuItem("Create copy", MenuItemType.Standard, () => { - lounge?.Open(Room.DeepClone()); + lounge?.OpenCopy(Room); }) }; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index cd1c8a0a64..08b4ae9db2 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -20,6 +20,7 @@ using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -63,6 +64,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge [Resolved] private IBindable ruleset { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + [CanBeNull] private IDisposable joiningRoomOperation { get; set; } @@ -310,6 +314,41 @@ namespace osu.Game.Screens.OnlinePlay.Lounge }); }); + /// + /// Copies a room and opens it as a fresh (not-yet-created) one. + /// + /// The room to copy. + public void OpenCopy(Room room) + { + Debug.Assert(room.RoomID.Value != null); + + if (joiningRoomOperation != null) + return; + + joiningRoomOperation = ongoingOperationTracker?.BeginOperation(); + + var req = new GetRoomRequest(room.RoomID.Value.Value); + + req.Success += r => + { + // ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not. + r.RoomID.Value = null; + + Open(r); + + joiningRoomOperation?.Dispose(); + joiningRoomOperation = null; + }; + + req.Failure += _ => + { + joiningRoomOperation?.Dispose(); + joiningRoomOperation = null; + }; + + api.Queue(req); + } + /// /// Push a room as a new subscreen. /// From 4839bd804466fb410884ee1f94eec40a84c86785 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Mar 2022 16:47:47 +0900 Subject: [PATCH 033/339] Notify if copying room fails Co-authored-by: Dean Herbert --- osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 08b4ae9db2..fb8647284f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -340,8 +340,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge joiningRoomOperation = null; }; - req.Failure += _ => + req.Failure += exception => { + Logger.Error(exception, "Couldn't create a copy of this room."); joiningRoomOperation?.Dispose(); joiningRoomOperation = null; }; From 0267aed846cdccaf08b9b382cf7363248a44c659 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 17:13:51 +0900 Subject: [PATCH 034/339] Change `ToMod` to return an `UnknownMod` rather than throw if a mod isn't available As seen by this kind of crash, having the `.ToMod` method throw can be very problematic and also hidden (as it is used inside of models in places where exceptions are not expected to occur). Given there are tens of usages of this method, returning a placeholder mod seems like a better idea than outright throwing. ``` An unhandled has occurred. System.InvalidOperationException: There is no mod in the ruleset (osu) matching the acronym AS. at osu.Game.Online.API.APIMod.ToMod(Ruleset ruleset) in /Users/dean/Projects/osu/osu.Game/Online/API/APIMod.cs:line 54 at osu.Game.Scoring.ScoreInfo.b__117_0(APIMod m) in /Users/dean/Projects/osu/osu.Game/Scoring/ScoreInfo.cs:line 193 at System.Linq.Enumerable.SelectArrayIterator`2.ToArray() at osu.Game.Scoring.ScoreInfo.get_Mods() in /Users/dean/Projects/osu/osu.Game/Scoring/ScoreInfo.cs:line 193 at osu.Game.Screens.Select.Leaderboards.BeatmapLeaderboard.<>c.b__40_2(ScoreInfo s) in /Users/dean/Projects/osu/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs:line 199 at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext() at osu.Game.Database.RealmObjectExtensions.Detach[T](IEnumerable`1 items) in /Users/dean/Projects/osu/osu.Game/Database/RealmObjectExtensions.cs:line 180 at osu.Game.Screens.Select.Leaderboards.BeatmapLeaderboard.<>c__DisplayClass40_0.g__localScoresChanged|1(IRealmCollection`1 sender, ChangeSet changes, Exception exception) in /Users/dean/Projects/osu/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs:line 209 at Realms.RealmCollectionBase`1.Realms.INotifiable.NotifyCallbacks(Nullable`1 changes, Nullable`1 exception) at Realms.NotifiableObjectHandleBase.NotifyObjectChanged(IntPtr managedHandle, IntPtr changes, IntPtr exception) ``` --- .../Online/TestAPIModJsonSerialization.cs | 14 ++++++++++ osu.Game/Online/API/APIMod.cs | 7 ++++- osu.Game/Rulesets/Mods/UnknownMod.cs | 27 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Rulesets/Mods/UnknownMod.cs diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index d5ea3e492c..e98ea98bb2 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -24,6 +24,20 @@ namespace osu.Game.Tests.Online [TestFixture] public class TestAPIModJsonSerialization { + [Test] + public void TestUnknownMod() + { + var apiMod = new APIMod { Acronym = "WNG" }; + + var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); + + var converted = deserialized?.ToMod(new TestRuleset()); + + Assert.That(converted, Is.TypeOf(typeof(UnknownMod))); + Assert.That(converted?.Type, Is.EqualTo(ModType.System)); + Assert.That(converted?.Acronym, Is.EqualTo("WNG??")); + } + [Test] public void TestAcronymIsPreserved() { diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 67041cae07..ef8637b8ed 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -8,6 +8,7 @@ using Humanizer; using MessagePack; using Newtonsoft.Json; using osu.Framework.Bindables; +using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -51,7 +52,10 @@ namespace osu.Game.Online.API Mod resultMod = ruleset.CreateModFromAcronym(Acronym); if (resultMod == null) - throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}."); + { + Logger.Log($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}."); + return new UnknownMod(Acronym); + } if (Settings.Count > 0) { @@ -98,4 +102,5 @@ namespace osu.Game.Online.API public int GetHashCode(KeyValuePair obj) => HashCode.Combine(obj.Key, ModUtils.GetSettingUnderlyingValue(obj.Value)); } } + } diff --git a/osu.Game/Rulesets/Mods/UnknownMod.cs b/osu.Game/Rulesets/Mods/UnknownMod.cs new file mode 100644 index 0000000000..013725e227 --- /dev/null +++ b/osu.Game/Rulesets/Mods/UnknownMod.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. + +namespace osu.Game.Rulesets.Mods +{ + public class UnknownMod : Mod + { + /// + /// The acronym of the mod which could not be resolved. + /// + public readonly string OriginalAcronym; + + public override string Name => $"Unknown mod ({OriginalAcronym})"; + public override string Acronym => $"{OriginalAcronym}??"; + public override string Description => "This mod could not be resolved by the game."; + public override double ScoreMultiplier => 0; + + public override ModType Type => ModType.System; + + public UnknownMod(string acronym) + { + OriginalAcronym = acronym; + } + + public override Mod DeepClone() => new UnknownMod(OriginalAcronym); + } +} From 1ee0be5e39c613e7bff13cac6bf65dc50dcd02b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 17:38:56 +0900 Subject: [PATCH 035/339] Ensure gameplay can't start when an `UnknownMod` is present --- .../Visual/Gameplay/UnknownModTestScene.cs | 26 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 6 +++++ 2 files changed, 32 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs diff --git a/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs b/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs new file mode 100644 index 0000000000..d3e52a0ed4 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.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 NUnit.Framework; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneUnknownMod : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + + [Test] + public void TestUnknownModDoesntEnterGameplay() + { + CreateModTest(new ModTestData + { + Beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).Beatmap, + Mod = new UnknownMod("WNG"), + PassCondition = () => Player.IsLoaded && !Player.LoadedBeatmapSuccessfully + }); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b6f576ff2b..875a96c810 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -185,6 +185,12 @@ namespace osu.Game.Screens.Play { var gameplayMods = Mods.Value.Select(m => m.DeepClone()).ToArray(); + if (gameplayMods.Any(m => m is UnknownMod)) + { + Logger.Log("Gameplay was started with an unknown mod applied.", level: LogLevel.Important); + return; + } + if (Beatmap.Value is DummyWorkingBeatmap) return; From 2eb3365f463618a8679a2ba4a06418bc44b1e4c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 17:50:05 +0900 Subject: [PATCH 036/339] Fix regressing issues when attempting to exit `Player` after an unsuccessful beatmap load --- .../Visual/Gameplay/UnknownModTestScene.cs | 3 ++ osu.Game/Screens/Play/Player.cs | 29 ++++++++++--------- osu.Game/Screens/Play/SubmittingPlayer.cs | 3 +- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs b/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs index d3e52a0ed4..c0f1112905 100644 --- a/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs +++ b/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs @@ -12,6 +12,9 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + /// + /// This test also covers the scenario of exiting Player after an unsuccessful beatmap load. + /// [Test] public void TestUnknownModDoesntEnterGameplay() { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 875a96c810..cb8f4b6020 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -994,24 +994,27 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { - if (!GameplayState.HasPassed && !GameplayState.HasFailed) - GameplayState.HasQuit = true; - screenSuspension?.RemoveAndDisposeImmediately(); failAnimationLayer?.RemoveFilters(); - // 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) + if (LoadedBeatmapSuccessfully) { - Score.ScoreInfo.Passed = false; - // potentially should be ScoreRank.F instead? this is the best alternative for now. - Score.ScoreInfo.Rank = ScoreRank.D; - } + if (!GameplayState.HasPassed && !GameplayState.HasFailed) + GameplayState.HasQuit = 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. - spectatorClient.EndPlaying(GameplayState); + // 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. + // Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method. + spectatorClient.EndPlaying(GameplayState); + } // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 2cf56be659..b1f2bccddf 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -119,7 +119,8 @@ namespace osu.Game.Screens.Play { bool exiting = base.OnExiting(next); - submitScore(Score.DeepClone()); + if (LoadedBeatmapSuccessfully) + submitScore(Score.DeepClone()); return exiting; } From 2e350a91ccc40487678c6b7ceff7d7c31aa93241 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 18:07:37 +0900 Subject: [PATCH 037/339] Fix non-matching filename --- .../Gameplay/{UnknownModTestScene.cs => TestSceneUnknownMod.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Visual/Gameplay/{UnknownModTestScene.cs => TestSceneUnknownMod.cs} (100%) diff --git a/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnknownMod.cs similarity index 100% rename from osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneUnknownMod.cs From 5fb51b578f52adf7a426f5871de0c1f0a833cc3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 19:09:50 +0900 Subject: [PATCH 038/339] Update dependencies Mainly for a `Clowd.Squirrel` bump to fix https://github.com/ppy/osu/discussions/17190. --- osu.Desktop/osu.Desktop.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 32ead231c7..a06484214b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,7 +24,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 bd4c3d3345..4ce29ab5c7 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,7 +3,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index acf1e8470b..0bcf533653 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -8,7 +8,7 @@ - + WinExe diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d86fbc693e..c106c373c4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -19,13 +19,13 @@ - + - - - + + + @@ -35,10 +35,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + From 8539f619c50eb06dc320fc1902e2c3cd0afd23f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 21:16:40 +0900 Subject: [PATCH 039/339] Update framework --- osu.Android.props | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 5b26b8f36e..c2788f9a48 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c37692f0d8..e485c69096 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From b59117caf643d04e1f35d8814811dc7c3bce2791 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 21:42:02 +0900 Subject: [PATCH 040/339] Update silly iOS dependencies --- osu.iOS.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.iOS.props b/osu.iOS.props index e485c69096..86cf1b229c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -79,7 +79,7 @@ - + @@ -89,6 +89,6 @@ - + From b67f9269f919d47647be5a72d71f1667500194bb Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 9 Mar 2022 18:13:56 +0000 Subject: [PATCH 041/339] Remove `NewChat` namespace --- .../Visual/Online/{NewChat => }/TestSceneChannelListing.cs | 4 ++-- osu.Game/Overlays/{NewChat => Chat/Listing}/ChannelListing.cs | 2 +- .../Overlays/{NewChat => Chat/Listing}/ChannelListingItem.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game.Tests/Visual/Online/{NewChat => }/TestSceneChannelListing.cs (97%) rename osu.Game/Overlays/{NewChat => Chat/Listing}/ChannelListing.cs (98%) rename osu.Game/Overlays/{NewChat => Chat/Listing}/ChannelListingItem.cs (99%) diff --git a/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelListing.cs similarity index 97% rename from osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs rename to osu.Game.Tests/Visual/Online/TestSceneChannelListing.cs index 8f955c0520..e521db1c9d 100644 --- a/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelListing.cs @@ -11,10 +11,10 @@ using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Chat; using osu.Game.Overlays; -using osu.Game.Overlays.NewChat; +using osu.Game.Overlays.Chat.Listing; using osuTK; -namespace osu.Game.Tests.Visual.Online.NewChat +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestSceneChannelListing : OsuTestScene diff --git a/osu.Game/Overlays/NewChat/ChannelListing.cs b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs similarity index 98% rename from osu.Game/Overlays/NewChat/ChannelListing.cs rename to osu.Game/Overlays/Chat/Listing/ChannelListing.cs index 3de6364303..9b8b45c85f 100644 --- a/osu.Game/Overlays/NewChat/ChannelListing.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Online.Chat; using osuTK; -namespace osu.Game.Overlays.NewChat +namespace osu.Game.Overlays.Chat.Listing { public class ChannelListing : VisibilityContainer { diff --git a/osu.Game/Overlays/NewChat/ChannelListingItem.cs b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs similarity index 99% rename from osu.Game/Overlays/NewChat/ChannelListingItem.cs rename to osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs index 77d5599c9b..9586730c6b 100644 --- a/osu.Game/Overlays/NewChat/ChannelListingItem.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs @@ -18,7 +18,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; using osuTK; -namespace osu.Game.Overlays.NewChat +namespace osu.Game.Overlays.Chat.Listing { public class ChannelListingItem : OsuClickableContainer, IFilterable { From d2983b74d53714176600b02387a8f2370b2d264f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 9 Mar 2022 20:15:34 +0100 Subject: [PATCH 042/339] Bump Moq in mobile test projects for parity --- osu.Game.Tests.Android/osu.Game.Tests.Android.csproj | 2 +- osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj index b45a3249ff..afafec6b1f 100644 --- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj +++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj @@ -79,7 +79,7 @@ - + 5.0.0 diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj index 97df9b2cd5..05b3cad6da 100644 --- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -48,7 +48,7 @@ - + From 5cb058a17d636ef70e2ea833a9401fd6dd41d332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 9 Mar 2022 20:17:46 +0100 Subject: [PATCH 043/339] Bump Realm in Android test project for parity --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index c2788f9a48..839f7882bf 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + From 2d8983383a02a830b4e1d84af34c795d077eb562 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 04:43:40 +0300 Subject: [PATCH 044/339] Revert `newMessagesArrived` changes and always schedule highlight In the case a message arrives and the chat overlay is hidden, clicking on the mention notification will not work as the `HighlightedMessage` bindable callback will execute before the scheduled `newMessagesArrived` logic (which was hanging since the message arrived until the chat overlay became open because of the notification). Simplify things by always scheduling the `HighlightedMessage` bindable callback. --- osu.Game/Overlays/Chat/DrawableChannel.cs | 82 ++++++++++++----------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index a73e61c52b..0cc3260e60 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -74,33 +74,37 @@ namespace osu.Game.Overlays.Chat } }, }; + + newMessagesArrived(Channel.Messages); + + Channel.NewMessagesArrived += newMessagesArrived; + Channel.MessageRemoved += messageRemoved; + Channel.PendingMessageResolved += pendingMessageResolved; } protected override void LoadComplete() { base.LoadComplete(); - addChatLines(Channel.Messages); - - Channel.NewMessagesArrived += newMessagesArrived; - Channel.MessageRemoved += messageRemoved; - Channel.PendingMessageResolved += pendingMessageResolved; - highlightedMessage = Channel.HighlightedMessage.GetBoundCopy(); highlightedMessage.BindValueChanged(m => { if (m.NewValue == null) return; - var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); - - if (chatLine != null) + // schedule highlight to ensure it performs after any pending "newMessagesArrived" calls. + Schedule(() => { - scroll.ScrollIntoView(chatLine); - chatLine.ScheduleHighlight(); - } + var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); - highlightedMessage.Value = null; + if (chatLine != null) + { + scroll.ScrollIntoView(chatLine); + chatLine.ScheduleHighlight(); + } + + highlightedMessage.Value = null; + }); }, true); } @@ -121,39 +125,18 @@ namespace osu.Game.Overlays.Chat Colour = colours.ChatBlue.Lighten(0.7f), }; - private void newMessagesArrived(IEnumerable newMessages) => Schedule(addChatLines, newMessages); - - private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => + private void newMessagesArrived(IEnumerable newMessages) => Schedule(() => { - var found = chatLines.LastOrDefault(c => c.Message == existing); - - if (found != null) - { - Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); - - ChatLineFlow.Remove(found); - found.Message = updated; - ChatLineFlow.Add(found); - } - }); - - private void messageRemoved(Message removed) => Schedule(() => - { - chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); - }); - - private void addChatLines(IEnumerable messages) - { - if (messages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id)) + if (newMessages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id)) { // there is a case (on initial population) that we may receive past messages and need to reorder. // easiest way is to just combine messages and recreate drawables (less worrying about day separators etc.) - messages = messages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList(); + newMessages = newMessages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList(); ChatLineFlow.Clear(); } // Add up to last Channel.MAX_HISTORY messages - var displayMessages = messages.Skip(Math.Max(0, messages.Count() - Channel.MAX_HISTORY)); + var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); Message lastMessage = chatLines.LastOrDefault()?.Message; @@ -192,9 +175,28 @@ namespace osu.Game.Overlays.Chat // due to the scroll adjusts from old messages removal above, a scroll-to-end must be enforced, // to avoid making the container think the user has scrolled back up and unwantedly disable auto-scrolling. - if (messages.Any(m => m is LocalMessage)) + if (newMessages.Any(m => m is LocalMessage)) scroll.ScrollToEnd(); - } + }); + + private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => + { + var found = chatLines.LastOrDefault(c => c.Message == existing); + + if (found != null) + { + Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); + + ChatLineFlow.Remove(found); + found.Message = updated; + ChatLineFlow.Add(found); + } + }); + + private void messageRemoved(Message removed) => Schedule(() => + { + chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + }); private IEnumerable chatLines => ChatLineFlow.Children.OfType(); From 93cf93943f5ff339443e7cffba97c09f51113ec9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 02:48:33 +0300 Subject: [PATCH 045/339] Schedule chat line highlight after children to handle non-loaded lines --- osu.Game/Overlays/Chat/DrawableChannel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 0cc3260e60..d5df6102f2 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -93,7 +93,8 @@ namespace osu.Game.Overlays.Chat return; // schedule highlight to ensure it performs after any pending "newMessagesArrived" calls. - Schedule(() => + // also schedule after children to ensure the scroll flow is updated with any new chat lines. + ScheduleAfterChildren(() => { var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); From 80c0df6af578962b91757d36e8a9de53664acfc3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 02:49:23 +0300 Subject: [PATCH 046/339] Scroll chat line to channel center We may eventually want that encapsulated within `ScrollIntoView`, as it would also come in handy for `GameplayLeaderboard`. --- .../Online/TestSceneStandAloneChatDisplay.cs | 17 ++++++++++------- .../Overlays/Chat/ChannelScrollContainer.cs | 6 ++++++ osu.Game/Overlays/Chat/DrawableChannel.cs | 3 ++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index c9837be934..17c818963e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -152,7 +152,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("highlight first message", () => { highlighted = testChannel.Messages[0]; - chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; + testChannel.HighlightedMessage.Value = highlighted; }); AddUntilStep("chat scrolled to first message", () => @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("highlight last message", () => { highlighted = testChannel.Messages[^1]; - chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; + testChannel.HighlightedMessage.Value = highlighted; }); AddUntilStep("chat scrolled to last message", () => @@ -182,19 +182,22 @@ namespace osu.Game.Tests.Visual.Online AddRepeatStep("highlight other random messages", () => { highlighted = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; - chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; + testChannel.HighlightedMessage.Value = highlighted; }, 10); } [Test] public void TestMessageHighlightingOnFilledChat() { + int index = 0; + fillChat(); - AddRepeatStep("highlight random messages", () => - { - chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; - }, 10); + AddStep("highlight first message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = 0]); + AddStep("highlight next message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = Math.Min(index + 1, testChannel.Messages.Count - 1)]); + AddStep("highlight last message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = testChannel.Messages.Count - 1]); + AddStep("highlight previous message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = Math.Max(index - 1, 0)]); + AddRepeatStep("highlight random messages", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = RNG.Next(0, testChannel.Messages.Count - 1)], 10); } /// diff --git a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs index 58b2b9a075..139c091f03 100644 --- a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs +++ b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs @@ -47,6 +47,12 @@ namespace osu.Game.Overlays.Chat updateTrackState(); } + public new void ScrollTo(float value, bool animated = true, double? distanceDecay = null) + { + base.ScrollTo(value, animated, distanceDecay); + updateTrackState(); + } + public new void ScrollIntoView(Drawable d, bool animated = true) { base.ScrollIntoView(d, animated); diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index d5df6102f2..433fb2ced7 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -100,7 +100,8 @@ namespace osu.Game.Overlays.Chat if (chatLine != null) { - scroll.ScrollIntoView(chatLine); + float center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2; + scroll.ScrollTo(Math.Clamp(center, 0, scroll.ScrollableExtent)); chatLine.ScheduleHighlight(); } From cf9671cafbe9f85dd8612a5a545360b534906fa8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 02:50:07 +0300 Subject: [PATCH 047/339] Increase highlight delay to 1500ms --- 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 97fda80492..9ae80a4e2b 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -220,7 +220,7 @@ namespace osu.Game.Overlays.Chat /// /// Scheduling is required to ensure the animation doesn't play until the chat line is in view and not scrolled away. /// - public void ScheduleHighlight() => Schedule(() => lineHighlightBackground.FadeTo(0.5f).FadeOut(1000, Easing.InQuint)); + public void ScheduleHighlight() => Schedule(() => lineHighlightBackground.FadeTo(0.5f).FadeOut(1500, Easing.InQuint)); private void updateMessageContent() { From d4de435eb2f228696d29147d351764114c1745ed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 02:50:27 +0300 Subject: [PATCH 048/339] Add test case for highlighting while chat overlay is hidden --- .../Visual/Online/TestSceneChatOverlay.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index a91cd53ec1..5ee17f80bd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -465,6 +465,38 @@ namespace osu.Game.Tests.Visual.Online AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); } + [Test] + public void TestHighlightWhileChatHidden() + { + Message message = null; + + AddStep("hide chat", () => chatOverlay.Hide()); + + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Send message in channel 1", () => + { + channel1.AddNewMessages(message = new Message + { + ChannelId = channel1.Id, + Content = "Message to highlight!", + Timestamp = DateTimeOffset.Now, + Sender = new APIUser + { + Id = 2, + Username = "Someone", + } + }); + }); + + AddStep("Highlight message and show chat", () => + { + chatOverlay.HighlightMessage(message); + chatOverlay.Show(); + }); + } + private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From b25c37ce62d96a3cb2640480cb203ff6d7741280 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 03:47:48 +0300 Subject: [PATCH 049/339] Instantiate highlight background container on animation Also removes the necessity of scheduling as it actually never worked as intended, `Scheduler` will still update even when the chat line is masked away, and the animation will never be held anyways. The new duration of the animation should be enough for long scrolls either way. --- osu.Game/Overlays/Chat/ChatLine.cs | 37 +++++++++++++---------- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 9ae80a4e2b..a1d8cd5d38 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -42,8 +42,6 @@ namespace osu.Game.Overlays.Chat protected virtual float TextSize => 20; - private Container lineHighlightBackground; - private Color4 usernameColour; private OsuSpriteText timestamp; @@ -146,15 +144,6 @@ namespace osu.Game.Overlays.Chat InternalChildren = new Drawable[] { - lineHighlightBackground = new Container - { - Alpha = 0f, - RelativeSizeAxes = Axes.Both, - Colour = usernameColour.Darken(1f), - CornerRadius = 2f, - Masking = true, - Child = new Box { RelativeSizeAxes = Axes.Both } - }, new Container { Size = new Vector2(MessagePadding, TextSize), @@ -214,13 +203,29 @@ namespace osu.Game.Overlays.Chat FinishTransforms(true); } + private Container highlight; + /// - /// Schedules a message highlight animation. + /// Performs a highlight animation on this . /// - /// - /// Scheduling is required to ensure the animation doesn't play until the chat line is in view and not scrolled away. - /// - public void ScheduleHighlight() => Schedule(() => lineHighlightBackground.FadeTo(0.5f).FadeOut(1500, Easing.InQuint)); + public void Highlight() + { + if (highlight?.IsAlive != true) + { + AddInternal(highlight = new Container + { + CornerRadius = 2f, + Masking = true, + RelativeSizeAxes = Axes.Both, + Colour = usernameColour.Darken(1f), + Depth = float.MaxValue, + Child = new Box { RelativeSizeAxes = Axes.Both } + }); + } + + highlight.FadeTo(0.5f).FadeOut(1500, Easing.InQuint); + highlight.Expire(); + } private void updateMessageContent() { diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 433fb2ced7..5086d08e91 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -102,7 +102,7 @@ namespace osu.Game.Overlays.Chat { float center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2; scroll.ScrollTo(Math.Clamp(center, 0, scroll.ScrollableExtent)); - chatLine.ScheduleHighlight(); + chatLine.Highlight(); } highlightedMessage.Value = null; From f229447453821c387da151c73eeee15cb3a47601 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 04:01:07 +0300 Subject: [PATCH 050/339] Add too many messages to better test long scrolls --- .../Visual/Online/TestSceneStandAloneChatDisplay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 17c818963e..860ef5d565 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.Online { int index = 0; - fillChat(); + fillChat(100); AddStep("highlight first message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = 0]); AddStep("highlight next message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = Math.Min(index + 1, testChannel.Messages.Count - 1)]); @@ -304,11 +304,11 @@ namespace osu.Game.Tests.Visual.Online checkScrolledToBottom(); } - private void fillChat() + private void fillChat(int count = 10) { AddStep("fill chat", () => { - for (int i = 0; i < 10; i++) + for (int i = 0; i < count; i++) { testChannel.AddNewMessages(new Message(messageIdSequence++) { From c36badab4bf9c636c9c5e516c805923d96ba5b31 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Mar 2022 10:26:09 +0900 Subject: [PATCH 051/339] Add per-ruleset score multipliers for classic scoring --- osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs | 1 + osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs | 2 ++ osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 2 ++ osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs | 2 ++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 7 ++++++- 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 2cc05826b4..9cd03dc869 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -7,5 +7,6 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { + protected override double ClassicScoreMultiplier => 28; } } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 48b377c794..e0b19d87e8 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -10,5 +10,7 @@ namespace osu.Game.Rulesets.Mania.Scoring protected override double DefaultAccuracyPortion => 0.99; protected override double DefaultComboPortion => 0.01; + + protected override double ClassicScoreMultiplier => 16; } } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 44118227d9..df38f0204a 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { + protected override double ClassicScoreMultiplier => 36; + protected override HitEvent CreateHitEvent(JudgementResult result) => base.CreateHitEvent(result).With((result as OsuHitCircleJudgementResult)?.CursorPositionAtHit); diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 1829ea2513..849b9c14bd 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -10,5 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring protected override double DefaultAccuracyPortion => 0.75; protected override double DefaultComboPortion => 0.25; + + protected override double ClassicScoreMultiplier => 22; } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index d5a5aa4592..7ca8a0fecf 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -76,6 +76,11 @@ namespace osu.Game.Rulesets.Scoring /// protected virtual double DefaultComboPortion => 0.7; + /// + /// An arbitrary multiplier to scale scores in the scoring mode. + /// + protected virtual double ClassicScoreMultiplier => 36; + private readonly double accuracyPortion; private readonly double comboPortion; @@ -246,7 +251,7 @@ namespace osu.Game.Rulesets.Scoring // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; - return Math.Pow(scaledStandardised * totalHitObjects, 2) * 36; + return Math.Pow(scaledStandardised * totalHitObjects, 2) * ClassicScoreMultiplier; } } From b38de6e580e61cc125d3305fbccdd4bf7b4da40c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 05:51:54 +0300 Subject: [PATCH 052/339] Add skinning support for welcome sprite text --- osu.Game/Screens/Menu/IntroWelcome.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 639591cfef..f85dbe9abe 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -100,13 +100,13 @@ namespace osu.Game.Screens.Menu private class WelcomeIntroSequence : Container { - private Sprite welcomeText; + private Drawable welcomeText; private Container scaleContainer; public LogoVisualisation LogoVisualisation { get; private set; } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(TextureStore textures, IAPIProvider api) { Origin = Anchor.Centre; Anchor = Anchor.Centre; @@ -135,15 +135,17 @@ namespace osu.Game.Screens.Menu Size = new Vector2(480), Colour = Color4.Black }, - welcomeText = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Texture = textures.Get(@"Intro/Welcome/welcome_text") - }, } }, }; + + if (api.LocalUser.Value.IsSupporter) + scaleContainer.Add(welcomeText = new SkinnableSprite(@"Intro/Welcome/welcome_text")); + else + scaleContainer.Add(welcomeText = new Sprite { Texture = textures.Get(@"Intro/Welcome/welcome_text") }); + + welcomeText.Anchor = Anchor.Centre; + welcomeText.Origin = Anchor.Centre; } protected override void LoadComplete() From b8ee786d774f2c504491ef41875f79b01c3636e0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 05:52:12 +0300 Subject: [PATCH 053/339] Add skinning support for "welcome" sample --- osu.Game/Screens/Menu/IntroWelcome.cs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index f85dbe9abe..27eaa7eb3a 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -13,7 +13,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Online.API; using osu.Game.Screens.Backgrounds; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Screens.Menu @@ -23,8 +26,11 @@ namespace osu.Game.Screens.Menu protected override string BeatmapHash => "64e00d7022195959bfa3109d09c2e2276c8f12f486b91fcf6175583e973b48f2"; protected override string BeatmapFile => "welcome.osz"; private const double delay_step_two = 2142; - private Sample welcome; - private Sample pianoReverb; + + private SkinnableSound skinnableWelcome; + private ISample welcome; + + private ISample pianoReverb; protected override string SeeyaSampleName => "Intro/Welcome/seeya"; protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false) @@ -40,10 +46,15 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load(AudioManager audio, IAPIProvider api) { if (MenuVoice.Value) - welcome = audio.Samples.Get(@"Intro/Welcome/welcome"); + { + if (api.LocalUser.Value.IsSupporter) + AddInternal(skinnableWelcome = new SkinnableSound(new SampleInfo(@"Intro/Welcome/welcome"))); + else + welcome = audio.Samples.Get(@"Intro/Welcome/welcome"); + } pianoReverb = audio.Samples.Get(@"Intro/Welcome/welcome_piano"); } @@ -65,7 +76,10 @@ namespace osu.Game.Screens.Menu AddInternal(intro); - welcome?.Play(); + if (skinnableWelcome != null) + skinnableWelcome.Play(); + else + welcome?.Play(); var reverbChannel = pianoReverb?.Play(); if (reverbChannel != null) From 2c1589e068dcc0af8d40ece788ed248fed782c8a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 05:52:20 +0300 Subject: [PATCH 054/339] Add skinning support for "seeya" sample --- osu.Game/Screens/Menu/IntroScreen.cs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index afe75c5ef7..65f9dde1f9 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -13,14 +13,17 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Utils; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.IO.Archives; +using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; +using osu.Game.Skinning; using osuTK; using osuTK.Graphics; using Realms; @@ -55,7 +58,8 @@ namespace osu.Game.Screens.Menu private const int exit_delay = 3000; - private Sample seeya; + private SkinnableSound skinnableSeeya; + private ISample seeya; protected virtual string SeeyaSampleName => "Intro/seeya"; @@ -90,14 +94,18 @@ namespace osu.Game.Screens.Menu private BeatmapManager beatmaps { get; set; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, Framework.Game game, RealmAccess realm) + private void load(OsuConfigManager config, Framework.Game game, RealmAccess realm, IAPIProvider api) { // prevent user from changing beatmap while the intro is still running. beatmap = Beatmap.BeginLease(false); MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); - seeya = audio.Samples.Get(SeeyaSampleName); + + if (api.LocalUser.Value.IsSupporter) + AddInternal(skinnableSeeya = new SkinnableSound(new SampleInfo(SeeyaSampleName))); + else + seeya = audio.Samples.Get(SeeyaSampleName); // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection. if (!MenuMusic.Value) @@ -201,7 +209,14 @@ namespace osu.Game.Screens.Menu // we also handle the exit transition. if (MenuVoice.Value) { - seeya.Play(); + // ensure samples have been updated after resume before playing. + ScheduleAfterChildren(() => + { + if (skinnableSeeya != null) + skinnableSeeya.Play(); + else + seeya.Play(); + }); // if playing the outro voice, we have more time to have fun with the background track. // initially fade to almost silent then ramp out over the remaining time. From eaef27595c71e8412f449345e9b81528fc6b60e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 15:33:50 +0900 Subject: [PATCH 055/339] Also mark `UnknownMod` as not user-playable --- osu.Game/Rulesets/Mods/UnknownMod.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Mods/UnknownMod.cs b/osu.Game/Rulesets/Mods/UnknownMod.cs index 013725e227..b426386d7a 100644 --- a/osu.Game/Rulesets/Mods/UnknownMod.cs +++ b/osu.Game/Rulesets/Mods/UnknownMod.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Mods public override string Description => "This mod could not be resolved by the game."; public override double ScoreMultiplier => 0; + public override bool UserPlayable => false; + public override ModType Type => ModType.System; public UnknownMod(string acronym) From 1a187d4dec553a0563e6baa75a5c15e282c34a7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 15:49:18 +0900 Subject: [PATCH 056/339] Add animation to checkbox when joning/leaving a channel --- .../Overlays/Chat/Listing/ChannelListingItem.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs index 9586730c6b..83362bc79d 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs @@ -81,9 +81,9 @@ namespace osu.Game.Overlays.Chat.Listing { checkbox = new SpriteIcon { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Left = 15 }, + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Icon = FontAwesome.Solid.Check, Size = new Vector2(icon_size), }, @@ -134,15 +134,19 @@ namespace osu.Game.Overlays.Chat.Listing channelJoined = channel.Joined.GetBoundCopy(); channelJoined.BindValueChanged(change => { + const double duration = 500; + if (change.NewValue) { - checkbox.Show(); + checkbox.FadeIn(duration, Easing.OutQuint); + checkbox.ScaleTo(1f, duration, Easing.OutElastic); channelText.Colour = Colour4.White; topicText.Colour = Colour4.White; } else { - checkbox.Hide(); + checkbox.FadeOut(duration, Easing.OutQuint); + checkbox.ScaleTo(0.8f, duration, Easing.OutQuint); channelText.Colour = colourProvider.Light3; topicText.Colour = colourProvider.Content2; } @@ -159,7 +163,7 @@ namespace osu.Game.Overlays.Chat.Listing protected override void OnHoverLost(HoverLostEvent e) { - hoverBox.Hide(); + hoverBox.FadeOut(300, Easing.OutQuint); base.OnHoverLost(e); } } From 46f2db1712beda4ae0a33d2c5fe54b6b10f19b0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 15:58:19 +0900 Subject: [PATCH 057/339] Move `ChannelListingItem` spacing into item so input is always handled by an item in the list Without this change, there would be a couple of pixels between each list item where nothing would be hovered. This is a pretty annoying UX which we should be avoiding we possible. --- osu.Game/Overlays/Chat/Listing/ChannelListing.cs | 2 -- osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs | 5 ++++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListing.cs b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs index 9b8b45c85f..732c78de15 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListing.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Game.Online.Chat; -using osuTK; namespace osu.Game.Overlays.Chat.Listing { @@ -51,7 +50,6 @@ namespace osu.Game.Overlays.Chat.Listing Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(3), Padding = new MarginPadding { Vertical = 13, diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs index 83362bc79d..526cbcda87 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs @@ -43,6 +43,8 @@ namespace osu.Game.Overlays.Chat.Listing private const float text_size = 18; private const float icon_size = 14; + private const float vertical_margin = 1.5f; + public ChannelListingItem(Channel channel) { this.channel = channel; @@ -54,7 +56,7 @@ namespace osu.Game.Overlays.Chat.Listing Masking = true; CornerRadius = 5; RelativeSizeAxes = Axes.X; - Height = 20; + Height = 20 + (vertical_margin * 2); Children = new Drawable[] { @@ -62,6 +64,7 @@ namespace osu.Game.Overlays.Chat.Listing { RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background3, + Margin = new MarginPadding { Vertical = vertical_margin }, Alpha = 0f, }, new GridContainer From c61397cc22c9ba9a1a78ab8ec1ba0ab64526a799 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Mar 2022 16:22:27 +0900 Subject: [PATCH 058/339] Fix whitespace/inspection --- osu.Game/Online/API/APIMod.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index ef8637b8ed..44b1c460d5 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -102,5 +102,4 @@ namespace osu.Game.Online.API public int GetHashCode(KeyValuePair obj) => HashCode.Combine(obj.Key, ModUtils.GetSettingUnderlyingValue(obj.Value)); } } - } From a1b7bf3986666e7ec07a226988386a3b84b75507 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 16:47:02 +0900 Subject: [PATCH 059/339] Use a minimum fade length for clamping rather than zero 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/Skinning/Default/KiaiFlash.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs index 46e0030034..506f679836 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Child .FadeTo(flash_opacity, EarlyActivationMilliseconds, Easing.OutQuint) .Then() - .FadeOut(Math.Max(0, timingPoint.BeatLength - fade_length), Easing.OutSine); + .FadeOut(Math.Max(fade_length, timingPoint.BeatLength - fade_length), Easing.OutSine); } } } From 671d614c92bde69e6465cc718fd69e561b34c42d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Mar 2022 17:54:31 +0900 Subject: [PATCH 060/339] Fix beatmap wedge mutating transforms incorrectly --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 40 +++++++++++---------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index ea531e89c8..59a48f4f9d 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -152,7 +152,6 @@ namespace osu.Game.Screens.Select public OsuSpriteText VersionLabel { get; private set; } public OsuSpriteText TitleLabel { get; private set; } public OsuSpriteText ArtistLabel { get; private set; } - public BeatmapSetOnlineStatusPill StatusPill { get; private set; } public FillFlowContainer MapperContainer { get; private set; } private Container difficultyColourBar; @@ -169,6 +168,12 @@ namespace osu.Game.Screens.Select [Resolved] private IBindable> mods { get; set; } + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + private ModSettingChangeTracker settingChangeTracker; public WedgeInfoText(WorkingBeatmap working, RulesetInfo userRuleset) @@ -181,7 +186,7 @@ namespace osu.Game.Screens.Select private IBindable starDifficulty; [BackgroundDependencyLoader] - private void load(OsuColour colours, LocalisationManager localisation, BeatmapDifficultyCache difficultyCache) + private void load(LocalisationManager localisation) { var beatmapInfo = working.BeatmapInfo; var metadata = beatmapInfo.Metadata; @@ -255,7 +260,7 @@ namespace osu.Game.Screens.Select Shear = -wedged_container_shear, Alpha = 0f, }, - StatusPill = new BeatmapSetOnlineStatusPill + new BeatmapSetOnlineStatusPill { AutoSizeAxes = Axes.Both, Anchor = Anchor.TopRight, @@ -264,6 +269,7 @@ namespace osu.Game.Screens.Select TextSize = 11, TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, Status = beatmapInfo.Status, + Alpha = string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? 0 : 1 } } }, @@ -311,22 +317,6 @@ namespace osu.Game.Screens.Select titleBinding.BindValueChanged(_ => setMetadata(metadata.Source)); artistBinding.BindValueChanged(_ => setMetadata(metadata.Source), true); - starRatingDisplay.DisplayedStars.BindValueChanged(s => - { - difficultyColourBar.Colour = colours.ForStarDifficulty(s.NewValue); - }, true); - - starDifficulty = difficultyCache.GetBindableDifficulty(beatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); - starDifficulty.BindValueChanged(s => - { - starRatingDisplay.FadeIn(transition_duration); - starRatingDisplay.Current.Value = s.NewValue ?? default; - }); - - // no difficulty means it can't have a status to show - if (string.IsNullOrEmpty(beatmapInfo.DifficultyName)) - StatusPill.Hide(); - addInfoLabels(); } @@ -334,6 +324,18 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); + starRatingDisplay.DisplayedStars.BindValueChanged(s => + { + difficultyColourBar.Colour = colours.ForStarDifficulty(s.NewValue); + }, true); + + starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); + starDifficulty.BindValueChanged(s => + { + starRatingDisplay.FadeIn(transition_duration); + starRatingDisplay.Current.Value = s.NewValue ?? default; + }); + mods.BindValueChanged(m => { settingChangeTracker?.Dispose(); From d5eca16b69eb01ec42a3b747e1367a3510f4775f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 18:08:07 +0900 Subject: [PATCH 061/339] Fix potential test failures due to slow realm refresh in `TestSceneScreenNavigation` As seen at https://github.com/ppy/osu/runs/5492345983?check_suite_focus=true. --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 8debb95f38..f2e6aa1e16 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -133,7 +133,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); - AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); + AddUntilStep("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); AddUntilStep("wait for score panel removal", () => scorePanel.Parent == null); } @@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); - AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); + AddUntilStep("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); AddUntilStep("wait for score panel removal", () => scorePanel.Parent == null); } From 45d537ef727e787afeaf044b9cfc12af50c29bf9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Mar 2022 18:50:53 +0900 Subject: [PATCH 062/339] Fix potential multiplayer crash with async disposal --- osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index d8fb448437..b49f373c7b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -175,7 +175,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.StartGameplay(); }); - private void onResultsReady() => resultsReady.SetResult(true); + private void onResultsReady() => Scheduler.Add(() => + { + resultsReady.SetResult(true); + }); protected override async Task PrepareScoreForResultsAsync(Score score) { From 2d135e5be61e2615fe045747a34c834d31d3c7fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 19:52:08 +0900 Subject: [PATCH 063/339] Explain purpose of `Schedule` call in `MultiplayerPlayer.onResultsReady` --- .../Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index b49f373c7b..78e7f3d987 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -175,10 +175,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.StartGameplay(); }); - private void onResultsReady() => Scheduler.Add(() => + private void onResultsReady() { - resultsReady.SetResult(true); - }); + // Schedule is required to ensure that `TaskCompletionSource.SetResult` is not called more than once. + // A scenario where this can occur is if this instance is not immediately disposed (ie. async disposal queue). + Schedule(() => resultsReady.SetResult(true)); + } protected override async Task PrepareScoreForResultsAsync(Score score) { From 2de779584482cb5a0795a338ad7666982ca6335a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Mar 2022 19:59:05 +0900 Subject: [PATCH 064/339] Fix always rolling up from zero Co-authored-by: Dean Herbert --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 59a48f4f9d..1e0aaf9c27 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -332,8 +332,13 @@ namespace osu.Game.Screens.Select starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); starDifficulty.BindValueChanged(s => { - starRatingDisplay.FadeIn(transition_duration); starRatingDisplay.Current.Value = s.NewValue ?? default; + + // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) + if (!starRatingDisplay.IsPresent) + starRatingDisplay.FinishTransforms(true); + + starRatingDisplay.FadeIn(transition_duration); }); mods.BindValueChanged(m => From 885cb3ce5bc0768a3e434436a5e540bfaff4a5b4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Mar 2022 20:02:58 +0900 Subject: [PATCH 065/339] Add current screen checks for added safety --- .../OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 78e7f3d987..043315c790 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; +using osu.Framework.Screens; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -171,6 +172,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onMatchStarted() => Scheduler.Add(() => { + if (!this.IsCurrentScreen()) + return; + loadingDisplay.Hide(); base.StartGameplay(); }); @@ -179,7 +183,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { // Schedule is required to ensure that `TaskCompletionSource.SetResult` is not called more than once. // A scenario where this can occur is if this instance is not immediately disposed (ie. async disposal queue). - Schedule(() => resultsReady.SetResult(true)); + Schedule(() => + { + if (!this.IsCurrentScreen()) + return; + + resultsReady.SetResult(true); + }); } protected override async Task PrepareScoreForResultsAsync(Score score) From e4ef540b5b321c2eef6932d44d6508055a851e52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 20:33:14 +0900 Subject: [PATCH 066/339] Fix intermittent failures on `TestBeatmapDownloadingFlow` due to slow realm refresh --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 2521d570f2..db988a544d 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Online [Test] public void TestBeatmapDownloadingFlow() { - AddAssert("ensure beatmap unavailable", () => !beatmaps.IsAvailableLocally(testBeatmapSet)); + AddUntilStep("ensure beatmap unavailable", () => !beatmaps.IsAvailableLocally(testBeatmapSet)); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); AddStep("start downloading", () => beatmapDownloader.Download(testBeatmapSet)); @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Online AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddUntilStep("wait for import", () => beatmaps.CurrentImport != null); - AddAssert("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet)); + AddUntilStep("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet)); addAvailabilityCheckStep("state is locally available", BeatmapAvailability.LocallyAvailable); } From 717ddbba4e0eb4e4a1f3a96ea83dfc17273d48f9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 16:30:20 +0300 Subject: [PATCH 067/339] Fix skin components toolbox not autosizing properly --- osu.Game/Overlays/SettingsToolboxGroup.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index b4178359a4..b9e5283a44 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -162,7 +162,9 @@ namespace osu.Game.Overlays Expanded.BindValueChanged(v => { - content.ClearTransforms(); + // clearing transforms can break autosizing, see: https://github.com/ppy/osu-framework/issues/5064 + if (v.NewValue != v.OldValue) + content.ClearTransforms(); if (v.NewValue) content.AutoSizeAxes = Axes.Y; From 68aedd63a7c9e0234de549988d876f1a2b8817d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 20:22:36 +0900 Subject: [PATCH 068/339] Move `SelectionHandler`'s `SelectedItems` binding to the base implementation Until now it was up to each implementation to connect `BlueprintContainer` to its `SelectionHandler`, which didn't make much sense at all. --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 1 + .../Edit/Compose/Components/EditorSelectionHandler.cs | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 130d7a015f..6dc6f20cfe 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -73,6 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionHandler = CreateSelectionHandler(); SelectionHandler.DeselectAll = deselectAll; + SelectionHandler.SelectedItems.BindTo(SelectedItems); AddRangeInternal(new[] { diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 5f6e8de557..7f693996a4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -29,11 +29,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // bring in updates from selection changes EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates); - SelectedItems.BindTo(EditorBeatmap.SelectedHitObjects); - SelectedItems.CollectionChanged += (sender, args) => - { - Scheduler.AddOnce(UpdateTernaryStates); - }; + SelectedItems.CollectionChanged += (sender, args) => Scheduler.AddOnce(UpdateTernaryStates); } protected override void DeleteItems(IEnumerable items) => EditorBeatmap.RemoveRange(items); From 8cbc8b944f1ec39622cabc2388db53bdacea3e9f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 17:25:13 +0300 Subject: [PATCH 069/339] Rescope scheduling and improve comment --- osu.Game/Screens/Menu/IntroScreen.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 65f9dde1f9..a6b54dd1f2 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -209,14 +209,15 @@ namespace osu.Game.Screens.Menu // we also handle the exit transition. if (MenuVoice.Value) { - // ensure samples have been updated after resume before playing. - ScheduleAfterChildren(() => + if (skinnableSeeya != null) { - if (skinnableSeeya != null) - skinnableSeeya.Play(); - else - seeya.Play(); - }); + // resuming a screen (i.e. calling OnResume) happens before the screen itself becomes alive, + // therefore skinnable samples may not be updated yet with the recently selected skin. + // schedule after children to ensure skinnable samples have processed skin changes before playing. + ScheduleAfterChildren(() => skinnableSeeya.Play()); + } + else + seeya.Play(); // if playing the outro voice, we have more time to have fun with the background track. // initially fade to almost silent then ramp out over the remaining time. From c72e8a8b5e37d3e959a491e926422ded94dd931a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 21:28:38 +0300 Subject: [PATCH 070/339] Add functionality to switch to successfully joined channel --- osu.Game/Online/Chat/ChannelManager.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 47e45e67d1..1404c0f333 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -420,10 +420,11 @@ namespace osu.Game.Online.Chat /// Joins a channel if it has not already been joined. Must be called from the update thread. /// /// The channel to join. + /// Switch to the joined channel on successful request. /// The joined channel. Note that this may not match the parameter channel as it is a backed object. - public Channel JoinChannel(Channel channel) => joinChannel(channel, true); + public Channel JoinChannel(Channel channel, bool switchToJoined = false) => joinChannel(channel, switchToJoined, true); - private Channel joinChannel(Channel channel, bool fetchInitialMessages = false) + private Channel joinChannel(Channel channel, bool switchToJoined = false, bool fetchInitialMessages = false) { if (channel == null) return null; @@ -439,7 +440,7 @@ namespace osu.Game.Online.Chat case ChannelType.Multiplayer: // join is implicit. happens when you join a multiplayer game. // this will probably change in the future. - joinChannel(channel, fetchInitialMessages); + joinChannel(channel, switchToJoined, fetchInitialMessages); return channel; case ChannelType.PM: @@ -460,7 +461,7 @@ namespace osu.Game.Online.Chat default: var req = new JoinChannelRequest(channel); - req.Success += () => joinChannel(channel, fetchInitialMessages); + req.Success += () => joinChannel(channel, switchToJoined, fetchInitialMessages); req.Failure += ex => LeaveChannel(channel); api.Queue(req); return channel; @@ -472,7 +473,8 @@ namespace osu.Game.Online.Chat this.fetchInitialMessages(channel); } - CurrentChannel.Value ??= channel; + if (switchToJoined || CurrentChannel.Value == null) + CurrentChannel.Value = channel; return channel; } From 5315ff794a30295e5f018d23a8e5c4026782d97c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 21:29:32 +0300 Subject: [PATCH 071/339] Handle non-existing channels on message highlighting gracefully --- osu.Game/Overlays/ChatOverlay.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index d6a1b23c46..a0b5e55014 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -309,10 +309,21 @@ namespace osu.Game.Overlays /// The message to highlight. public void HighlightMessage(Message message) { - if (channelManager.CurrentChannel.Value.Id != message.ChannelId) - channelManager.CurrentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); + Channel targetChannel; - channelManager.CurrentChannel.Value.HighlightedMessage.Value = message; + if (channelManager.CurrentChannel.Value.Id == message.ChannelId) + targetChannel = channelManager.CurrentChannel.Value; + else + { + targetChannel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == message.ChannelId); + + if (targetChannel != null) + channelManager.CurrentChannel.Value = targetChannel; + else + targetChannel = channelManager.JoinChannel(channelManager.AvailableChannels.SingleOrDefault(c => c.Id == message.ChannelId), true); + } + + targetChannel.HighlightedMessage.Value = message; } private float startDragChatHeight; From e41937083003a2b9c13ac0abd449c8068e574686 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 21:30:02 +0300 Subject: [PATCH 072/339] Add test coverage for highlighting message on left channel --- .../Visual/Online/TestSceneChatOverlay.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 5ee17f80bd..906b3faa98 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -463,6 +463,36 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); + } + + [Test] + public void TestHighlightOnLeftChannel() + { + Message message = null; + + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Join channel 2", () => channelManager.JoinChannel(channel2)); + AddStep("Send message in channel 2", () => + { + channel2.AddNewMessages(message = new Message + { + ChannelId = channel2.Id, + Content = "Message to highlight!", + Timestamp = DateTimeOffset.Now, + Sender = new APIUser + { + Id = 2, + Username = "Someone", + } + }); + }); + AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); + + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); } [Test] From 8086f734514ba70ae9997fd2927af2e5dd432a45 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 22:50:02 +0300 Subject: [PATCH 073/339] Revert "Add functionality to switch to successfully joined channel" This reverts commit c72e8a8b5e37d3e959a491e926422ded94dd931a. --- osu.Game/Online/Chat/ChannelManager.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 1404c0f333..47e45e67d1 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -420,11 +420,10 @@ namespace osu.Game.Online.Chat /// Joins a channel if it has not already been joined. Must be called from the update thread. /// /// The channel to join. - /// Switch to the joined channel on successful request. /// The joined channel. Note that this may not match the parameter channel as it is a backed object. - public Channel JoinChannel(Channel channel, bool switchToJoined = false) => joinChannel(channel, switchToJoined, true); + public Channel JoinChannel(Channel channel) => joinChannel(channel, true); - private Channel joinChannel(Channel channel, bool switchToJoined = false, bool fetchInitialMessages = false) + private Channel joinChannel(Channel channel, bool fetchInitialMessages = false) { if (channel == null) return null; @@ -440,7 +439,7 @@ namespace osu.Game.Online.Chat case ChannelType.Multiplayer: // join is implicit. happens when you join a multiplayer game. // this will probably change in the future. - joinChannel(channel, switchToJoined, fetchInitialMessages); + joinChannel(channel, fetchInitialMessages); return channel; case ChannelType.PM: @@ -461,7 +460,7 @@ namespace osu.Game.Online.Chat default: var req = new JoinChannelRequest(channel); - req.Success += () => joinChannel(channel, switchToJoined, fetchInitialMessages); + req.Success += () => joinChannel(channel, fetchInitialMessages); req.Failure += ex => LeaveChannel(channel); api.Queue(req); return channel; @@ -473,8 +472,7 @@ namespace osu.Game.Online.Chat this.fetchInitialMessages(channel); } - if (switchToJoined || CurrentChannel.Value == null) - CurrentChannel.Value = channel; + CurrentChannel.Value ??= channel; return channel; } From a31611bdec45c0378e9486cfb090733f7d12b2e0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 22:54:47 +0300 Subject: [PATCH 074/339] Improve channel switching flow in `HighlightMessage` --- .../Visual/Online/TestSceneChatOverlay.cs | 8 +++---- osu.Game/Online/Chat/MessageNotifier.cs | 22 ++++++++++--------- osu.Game/Overlays/ChatOverlay.cs | 20 ++++++++--------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 906b3faa98..00ff6a9576 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -435,7 +435,7 @@ namespace osu.Game.Tests.Visual.Online }); }); - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel1)); } [Test] @@ -462,7 +462,7 @@ namespace osu.Game.Tests.Visual.Online }); }); - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel2)); AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); } @@ -491,7 +491,7 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel2)); AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); } @@ -522,7 +522,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Highlight message and show chat", () => { - chatOverlay.HighlightMessage(message); + chatOverlay.HighlightMessage(message, channel1); chatOverlay.Show(); }); } diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 90b6d927c7..bcfec3cc0f 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -99,7 +99,7 @@ namespace osu.Game.Online.Chat if (checkForPMs(channel, message)) continue; - checkForMentions(message); + checkForMentions(channel, message); } } @@ -114,15 +114,15 @@ namespace osu.Game.Online.Chat if (!notifyOnPrivateMessage.Value || channel.Type != ChannelType.PM) return false; - notifications.Post(new PrivateMessageNotification(message)); + notifications.Post(new PrivateMessageNotification(message, channel)); return true; } - private void checkForMentions(Message message) + private void checkForMentions(Channel channel, Message message) { if (!notifyOnUsername.Value || !CheckContainsUsername(message.Content, localUser.Value.Username)) return; - notifications.Post(new MentionNotification(message)); + notifications.Post(new MentionNotification(message, channel)); } /// @@ -138,8 +138,8 @@ namespace osu.Game.Online.Chat public class PrivateMessageNotification : HighlightMessageNotification { - public PrivateMessageNotification(Message message) - : base(message) + public PrivateMessageNotification(Message message, Channel channel) + : base(message, channel) { Icon = FontAwesome.Solid.Envelope; Text = $"You received a private message from '{message.Sender.Username}'. Click to read it!"; @@ -148,8 +148,8 @@ namespace osu.Game.Online.Chat public class MentionNotification : HighlightMessageNotification { - public MentionNotification(Message message) - : base(message) + public MentionNotification(Message message, Channel channel) + : base(message, channel) { Icon = FontAwesome.Solid.At; Text = $"Your name was mentioned in chat by '{message.Sender.Username}'. Click to find out why!"; @@ -158,12 +158,14 @@ namespace osu.Game.Online.Chat public abstract class HighlightMessageNotification : SimpleNotification { - protected HighlightMessageNotification(Message message) + protected HighlightMessageNotification(Message message, Channel channel) { this.message = message; + this.channel = channel; } private readonly Message message; + private readonly Channel channel; public override bool IsImportant => false; @@ -175,7 +177,7 @@ namespace osu.Game.Online.Chat Activated = delegate { notificationOverlay.Hide(); - chatOverlay.HighlightMessage(message); + chatOverlay.HighlightMessage(message, channel); chatOverlay.Show(); return true; diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index a0b5e55014..3764ac42fc 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osuTK; using osuTK.Graphics; @@ -307,23 +308,20 @@ namespace osu.Game.Overlays /// Highlights a certain message in the specified channel. /// /// The message to highlight. - public void HighlightMessage(Message message) + /// The channel containing the message. + public void HighlightMessage(Message message, Channel channel) { - Channel targetChannel; + Debug.Assert(channel.Id == message.ChannelId); - if (channelManager.CurrentChannel.Value.Id == message.ChannelId) - targetChannel = channelManager.CurrentChannel.Value; - else + if (currentChannel.Value.Id != channel.Id) { - targetChannel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == message.ChannelId); + if (!channel.Joined.Value) + channel = channelManager.JoinChannel(channel); - if (targetChannel != null) - channelManager.CurrentChannel.Value = targetChannel; - else - targetChannel = channelManager.JoinChannel(channelManager.AvailableChannels.SingleOrDefault(c => c.Id == message.ChannelId), true); + channelManager.CurrentChannel.Value = channel; } - targetChannel.HighlightedMessage.Value = message; + channel.HighlightedMessage.Value = message; } private float startDragChatHeight; From d07e3101ea5a040b00c3fc1f66bf1b131aa2dba8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 23:06:58 +0300 Subject: [PATCH 075/339] Improve message highlight handling in `DrawableChannel` --- osu.Game/Overlays/Chat/DrawableChannel.cs | 46 ++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 5086d08e91..1d9f8c7ab7 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -87,29 +87,31 @@ namespace osu.Game.Overlays.Chat base.LoadComplete(); highlightedMessage = Channel.HighlightedMessage.GetBoundCopy(); - highlightedMessage.BindValueChanged(m => - { - if (m.NewValue == null) - return; - - // schedule highlight to ensure it performs after any pending "newMessagesArrived" calls. - // also schedule after children to ensure the scroll flow is updated with any new chat lines. - ScheduleAfterChildren(() => - { - var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); - - if (chatLine != null) - { - float center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2; - scroll.ScrollTo(Math.Clamp(center, 0, scroll.ScrollableExtent)); - chatLine.Highlight(); - } - - highlightedMessage.Value = null; - }); - }, true); + highlightedMessage.BindValueChanged(_ => processMessageHighlighting(), true); } + /// + /// Processes any pending message in . + /// + /// + /// is for ensuring the scroll flow has updated with any new chat lines. + /// + private void processMessageHighlighting() => ScheduleAfterChildren(() => + { + if (highlightedMessage.Value == null) + return; + + var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(highlightedMessage.Value)); + if (chatLine == null) + return; + + float center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2; + scroll.ScrollTo(Math.Clamp(center, 0, scroll.ScrollableExtent)); + chatLine.Highlight(); + + highlightedMessage.Value = null; + }); + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -179,6 +181,8 @@ namespace osu.Game.Overlays.Chat // to avoid making the container think the user has scrolled back up and unwantedly disable auto-scrolling. if (newMessages.Any(m => m is LocalMessage)) scroll.ScrollToEnd(); + + processMessageHighlighting(); }); private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => From 32c7a023f8206ec6cde60e4d1d2dc9641eb06a66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Mar 2022 19:54:13 +0900 Subject: [PATCH 076/339] Make `OsuGame.ScreenChanged` `private` and non-`virtual` Just reducing complexity scope here. --- osu.Game/OsuGame.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fb81e4fd14..7cd043083a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1140,7 +1140,7 @@ namespace osu.Game MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; } - protected virtual void ScreenChanged(IScreen current, IScreen newScreen) + private void screenChanged(IScreen current, IScreen newScreen) { skinEditor.Reset(); @@ -1187,11 +1187,11 @@ namespace osu.Game } } - private void screenPushed(IScreen lastScreen, IScreen newScreen) => ScreenChanged(lastScreen, newScreen); + private void screenPushed(IScreen lastScreen, IScreen newScreen) => screenChanged(lastScreen, newScreen); private void screenExited(IScreen lastScreen, IScreen newScreen) { - ScreenChanged(lastScreen, newScreen); + screenChanged(lastScreen, newScreen); if (newScreen == null) Exit(); From ac55fea3c953a6c81326308200d4b905cd921c3b Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 11 Mar 2022 14:04:12 +0100 Subject: [PATCH 077/339] Confine the host cursor to area of 'everything' scaling container --- .../Graphics/Containers/ScalingContainer.cs | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index d331b818a1..58d18e1b21 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -6,6 +6,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Framework.Layout; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Configuration; using osu.Game.Screens; @@ -66,7 +68,7 @@ namespace osu.Game.Graphics.Containers this.targetMode = targetMode; RelativeSizeAxes = Axes.Both; - InternalChild = sizableContainer = new AlwaysInputContainer + InternalChild = sizableContainer = new SizeableAlwaysInputContainer(targetMode == ScalingMode.Everything) { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, @@ -209,13 +211,50 @@ namespace osu.Game.Graphics.Containers } } - private class AlwaysInputContainer : Container + private class SizeableAlwaysInputContainer : Container { + [Resolved] + private GameHost host { get; set; } + + [Resolved] + private ISafeArea safeArea { get; set; } + + private readonly bool confineHostCursor; + private readonly LayoutValue cursorRectCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - public AlwaysInputContainer() + /// + /// Container used for sizing/positioning purposes in . Always receives mouse input. + /// + /// Whether to confine the host cursor to the draw area of this container. + /// Cursor confinement will abide by the setting. + public SizeableAlwaysInputContainer(bool confineHostCursor) { RelativeSizeAxes = Axes.Both; + this.confineHostCursor = confineHostCursor; + + if (confineHostCursor) + AddLayout(cursorRectCache); + } + + protected override void Update() + { + base.Update(); + + if (confineHostCursor && !cursorRectCache.IsValid) + { + updateHostCursorConfineRect(); + cursorRectCache.Validate(); + } + } + + private void updateHostCursorConfineRect() + { + if (host.Window == null) return; + + bool coversWholeScreen = Size == Vector2.One && safeArea.SafeAreaPadding.Value.Total == Vector2.Zero; + host.Window.CursorConfineRect = coversWholeScreen ? (RectangleF?)null : ToScreenSpace(DrawRectangle).AABBFloat; } } } From 9a1ade4f7907b695aeaa0d5ca19d18e63c4fa554 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Mar 2022 23:08:40 +0900 Subject: [PATCH 078/339] Refactor `SkinEditor` to support switching target screens without full reload --- osu.Game/OsuGame.cs | 4 +-- osu.Game/Skinning/Editor/SkinEditor.cs | 29 ++++++++++----- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 35 +++++++++++++++---- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7cd043083a..ae117d03d2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1142,8 +1142,6 @@ namespace osu.Game private void screenChanged(IScreen current, IScreen newScreen) { - skinEditor.Reset(); - switch (newScreen) { case IntroScreen intro: @@ -1185,6 +1183,8 @@ namespace osu.Game else BackButton.Hide(); } + + skinEditor.SetTarget((Screen)newScreen); } private void screenPushed(IScreen lastScreen, IScreen newScreen) => screenChanged(lastScreen, newScreen); diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index ae5cbc95f0..cd21507128 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -28,7 +28,7 @@ namespace osu.Game.Skinning.Editor protected override bool StartHidden => true; - private readonly Drawable targetScreen; + private Drawable targetScreen; private OsuTextFlowContainer headerText; @@ -42,11 +42,13 @@ namespace osu.Game.Skinning.Editor private bool hasBegunMutating; + private Container blueprintContainerContainer; + public SkinEditor(Drawable targetScreen) { - this.targetScreen = targetScreen; - RelativeSizeAxes = Axes.Both; + + UpdateTargetScreen(targetScreen); } [BackgroundDependencyLoader] @@ -113,13 +115,9 @@ namespace osu.Game.Skinning.Editor Origin = Anchor.CentreLeft, RequestPlacement = placeComponent }, - new Container + blueprintContainerContainer = new Container { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new SkinBlueprintContainer(targetScreen), - } }, } } @@ -147,6 +145,21 @@ namespace osu.Game.Skinning.Editor }, true); } + public void UpdateTargetScreen(Drawable targetScreen) + { + this.targetScreen = targetScreen; + + Scheduler.AddOnce(loadBlueprintContainer); + + void loadBlueprintContainer() + { + blueprintContainerContainer.Children = new Drawable[] + { + new SkinBlueprintContainer(targetScreen), + }; + } + } + private void skinChanged() { headerText.Clear(); diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 61c363b019..08dad2a146 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.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 JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -8,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; @@ -114,15 +116,36 @@ namespace osu.Game.Skinning.Editor } /// - /// Exit any existing skin editor due to the game state changing. + /// Set a new target screen which will be used to find skinnable components. /// - public void Reset() + public void SetTarget(Screen screen) { - skinEditor?.Save(); - skinEditor?.Hide(); - skinEditor?.Expire(); + if (skinEditor == null) return; - skinEditor = null; + skinEditor.Save(); + + // AddOnce with paramter will ensure the newest target is loaded if there is any overlap. + Scheduler.AddOnce(setTarget, screen); + } + + private void setTarget(Screen target) + { + Debug.Assert(skinEditor != null); + + if (!target.IsLoaded) + { + Scheduler.AddOnce(setTarget, target); + return; + } + + if (skinEditor.State.Value == Visibility.Visible) + skinEditor.UpdateTargetScreen(target); + else + { + skinEditor.Hide(); + skinEditor.Expire(); + skinEditor = null; + } } } } From 3db42dd77254df799efa9334a2389efacf31e622 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 20:00:32 +0900 Subject: [PATCH 079/339] Allow skin editor to target different target containers for placement purposes --- osu.Game/Skinning/Editor/SkinEditor.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index ae5cbc95f0..36b06f01d2 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -171,7 +171,12 @@ namespace osu.Game.Skinning.Editor private void placeComponent(Type type) { - var targetContainer = getTarget(SkinnableTarget.MainHUDComponents); + var target = availableTargets.FirstOrDefault()?.Target; + + if (target == null) + return; + + var targetContainer = getTarget(target.Value); if (targetContainer == null) return; From 5b70139b333bbe03fa6781423f3bec955a4fa6c1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Mar 2022 18:32:32 +0300 Subject: [PATCH 080/339] Avoid running message highlight processing more than once --- 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 1d9f8c7ab7..52878199f0 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Chat /// /// is for ensuring the scroll flow has updated with any new chat lines. /// - private void processMessageHighlighting() => ScheduleAfterChildren(() => + private void processMessageHighlighting() => SchedulerAfterChildren.AddOnce(() => { if (highlightedMessage.Value == null) return; From 53c57661c70a1da9836fd2eb4418ad58a0b45df9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Mar 2022 18:34:22 +0300 Subject: [PATCH 081/339] Move implementtaion detail to inline comment --- 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 52878199f0..632517aa31 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -93,9 +93,7 @@ namespace osu.Game.Overlays.Chat /// /// Processes any pending message in . /// - /// - /// is for ensuring the scroll flow has updated with any new chat lines. - /// + // ScheduleAfterChildren is for ensuring the scroll flow has updated with any new chat lines. private void processMessageHighlighting() => SchedulerAfterChildren.AddOnce(() => { if (highlightedMessage.Value == null) From 0b74bde653c2e8be85b4467b8e3ed3db5fe4f5ed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 13 Mar 2022 08:21:52 +0300 Subject: [PATCH 082/339] Disable taiko playfield aspect ratio lock on "Classic" mod --- osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 1 + osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 7 ++++++- .../UI/TaikoPlayfieldAdjustmentContainer.cs | 14 ++++++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 6520517039..5a6f57bc36 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; + drawableTaikoRuleset.LockPlayfieldAspect.Value = false; } public void Update(Playfield playfield) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 824b95639b..2efc4176f5 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -29,6 +29,8 @@ namespace osu.Game.Rulesets.Taiko.UI { public new BindableDouble TimeRange => base.TimeRange; + public readonly BindableBool LockPlayfieldAspect = new BindableBool(true); + protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; protected override bool UserScrollSpeedAdjustment => false; @@ -70,7 +72,10 @@ namespace osu.Game.Rulesets.Taiko.UI return ControlPoints[result]; } - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer + { + LockPlayfieldAspect = { BindTarget = LockPlayfieldAspect } + }; protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 1041456020..9cf530e903 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.UI; -using osuTK; namespace osu.Game.Rulesets.Taiko.UI { @@ -13,16 +13,22 @@ namespace osu.Game.Rulesets.Taiko.UI private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768; private const float default_aspect = 16f / 9f; + public readonly IBindable LockPlayfieldAspect = new BindableBool(true); + protected override void Update() { base.Update(); - float aspectAdjust = Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; - Size = new Vector2(1, default_relative_height * aspectAdjust); + float height = default_relative_height; + + if (LockPlayfieldAspect.Value) + height *= Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; + + Height = height; // Position the taiko playfield exactly one playfield from the top of the screen. RelativePositionAxes = Axes.Y; - Y = Size.Y; + Y = height; } } } From d1a9b88fe7756e6925b26511459840009b3180c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Mar 2022 16:05:45 +0900 Subject: [PATCH 083/339] Fix typo in comment Co-authored-by: Salman Ahmed --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 08dad2a146..abd8272633 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -124,7 +124,7 @@ namespace osu.Game.Skinning.Editor skinEditor.Save(); - // AddOnce with paramter will ensure the newest target is loaded if there is any overlap. + // AddOnce with parameter will ensure the newest target is loaded if there is any overlap. Scheduler.AddOnce(setTarget, screen); } From f95e753adbfb170317c68ba19155f16e1b3a10a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Mar 2022 16:10:06 +0900 Subject: [PATCH 084/339] Rename double-container variable name --- osu.Game/Skinning/Editor/SkinEditor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index cd21507128..829faab116 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -42,7 +42,7 @@ namespace osu.Game.Skinning.Editor private bool hasBegunMutating; - private Container blueprintContainerContainer; + private Container content; public SkinEditor(Drawable targetScreen) { @@ -115,7 +115,7 @@ namespace osu.Game.Skinning.Editor Origin = Anchor.CentreLeft, RequestPlacement = placeComponent }, - blueprintContainerContainer = new Container + content = new Container { RelativeSizeAxes = Axes.Both, }, @@ -153,7 +153,7 @@ namespace osu.Game.Skinning.Editor void loadBlueprintContainer() { - blueprintContainerContainer.Children = new Drawable[] + content.Children = new Drawable[] { new SkinBlueprintContainer(targetScreen), }; From c99397f75abc9b431954f7b90176e1d44dd6e51e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Mar 2022 23:27:27 +0900 Subject: [PATCH 085/339] Add the ability to add settings to skinnable elements --- osu.Game/Extensions/DrawableExtensions.cs | 13 +++++++++++++ osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 14 ++++++++++++++ osu.Game/Skinning/ISkinnableDrawable.cs | 20 ++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 005804789e..d1aba2bfe3 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -1,8 +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 Humanizer; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osuTK; @@ -59,8 +62,18 @@ namespace osu.Game.Extensions component.Origin = info.Origin; if (component is ISkinnableDrawable skinnable) + { skinnable.UsesFixedAnchor = info.UsesFixedAnchor; + foreach (var (_, property) in component.GetSettingsSourceProperties()) + { + if (!info.Settings.TryGetValue(property.Name.Underscore(), out object settingValue)) + continue; + + skinnable.CopyAdjustedSetting((IBindable)property.GetValue(component), settingValue); + } + } + if (component is Container container) { foreach (var child in info.Children) diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index a2b84c79af..5a51e7f455 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -4,11 +4,15 @@ using System; using System.Collections.Generic; using System.Linq; +using Humanizer; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Skinning; +using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Play.HUD @@ -34,6 +38,8 @@ namespace osu.Game.Screens.Play.HUD /// public bool UsesFixedAnchor { get; set; } + public Dictionary Settings { get; set; } = new Dictionary(); + public List Children { get; } = new List(); [JsonConstructor] @@ -58,6 +64,14 @@ namespace osu.Game.Screens.Play.HUD if (component is ISkinnableDrawable skinnable) UsesFixedAnchor = skinnable.UsesFixedAnchor; + foreach (var (_, property) in component.GetSettingsSourceProperties()) + { + var bindable = (IBindable)property.GetValue(component); + + if (!bindable.IsDefault) + Settings.Add(property.Name.Underscore(), ModUtils.GetSettingUnderlyingValue(bindable)); + } + if (component is Container container) { foreach (var child in container.OfType().OfType()) diff --git a/osu.Game/Skinning/ISkinnableDrawable.cs b/osu.Game/Skinning/ISkinnableDrawable.cs index 60b40982e5..3fc6a2fdd8 100644 --- a/osu.Game/Skinning/ISkinnableDrawable.cs +++ b/osu.Game/Skinning/ISkinnableDrawable.cs @@ -1,6 +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 osu.Framework.Bindables; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; namespace osu.Game.Skinning @@ -21,5 +24,22 @@ namespace osu.Game.Skinning /// If , a fixed anchor point has been defined. /// bool UsesFixedAnchor { get; set; } + + void CopyAdjustedSetting(IBindable target, object source) + { + if (source is IBindable sourceBindable) + { + // copy including transfer of default values. + target.BindTo(sourceBindable); + target.UnbindFrom(sourceBindable); + } + else + { + if (!(target is IParseable parseable)) + throw new InvalidOperationException($"Bindable type {target.GetType().ReadableName()} is not {nameof(IParseable)}."); + + parseable.Parse(source); + } + } } } From 8d1ee28e6711eec49735625f8e148993aebbec4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Mar 2022 23:30:46 +0900 Subject: [PATCH 086/339] Add settings modification UI to skin editor --- .../Rulesets/Edit/ScrollingToolboxGroup.cs | 4 ++- osu.Game/Skinning/Editor/SkinEditor.cs | 28 ++++++++++++++++++- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 4 ++- .../Skinning/Editor/SkinSettingsToolbox.cs | 23 +++++++++++++++ 4 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Skinning/Editor/SkinSettingsToolbox.cs diff --git a/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs b/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs index 9998a997b3..98e026c49a 100644 --- a/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs +++ b/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Edit { protected readonly OsuScrollContainer Scroll; + protected readonly FillFlowContainer FillFlow; + protected override Container Content { get; } public ScrollingToolboxGroup(string title, float scrollAreaHeight) @@ -20,7 +22,7 @@ namespace osu.Game.Rulesets.Edit { RelativeSizeAxes = Axes.X, Height = scrollAreaHeight, - Child = Content = new FillFlowContainer + Child = Content = FillFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 459fbf3be5..ef26682c03 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,10 +12,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Skinning.Editor @@ -44,6 +47,8 @@ namespace osu.Game.Skinning.Editor private Container content; + private EditorToolboxGroup settingsToolbox; + public SkinEditor(Drawable targetScreen) { RelativeSizeAxes = Axes.Both; @@ -103,7 +108,8 @@ namespace osu.Game.Skinning.Editor ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), - new Dimension() + new Dimension(), + new Dimension(GridSizeMode.AutoSize), }, Content = new[] { @@ -119,6 +125,11 @@ namespace osu.Game.Skinning.Editor { RelativeSizeAxes = Axes.Both, }, + settingsToolbox = new SkinSettingsToolbox + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } } } } @@ -143,12 +154,15 @@ namespace osu.Game.Skinning.Editor hasBegunMutating = false; Scheduler.AddOnce(skinChanged); }, true); + + SelectedComponents.BindCollectionChanged(selectionChanged); } public void UpdateTargetScreen(Drawable targetScreen) { this.targetScreen = targetScreen; + SelectedComponents.Clear(); Scheduler.AddOnce(loadBlueprintContainer); void loadBlueprintContainer() @@ -210,6 +224,18 @@ namespace osu.Game.Skinning.Editor SelectedComponents.Add(component); } + private void selectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + settingsToolbox.Clear(); + + var first = SelectedComponents.OfType().FirstOrDefault(); + + if (first != null) + { + settingsToolbox.Children = first.CreateSettingsControls().ToArray(); + } + } + private IEnumerable availableTargets => targetScreen.ChildrenOfType(); private ISkinnableTarget getTarget(SkinnableTarget target) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index abd8272633..08cdbf0aa9 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -101,9 +101,11 @@ namespace osu.Game.Skinning.Editor private void editorVisibilityChanged(ValueChangedEvent visibility) { + const float toolbar_padding_requirement = 0.18f; + if (visibility.NewValue == Visibility.Visible) { - target.SetCustomRect(new RectangleF(0.18f, 0.1f, VISIBLE_TARGET_SCALE, VISIBLE_TARGET_SCALE), true); + target.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); } else { diff --git a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs b/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs new file mode 100644 index 0000000000..c0ef8e7316 --- /dev/null +++ b/osu.Game/Skinning/Editor/SkinSettingsToolbox.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; +using osu.Game.Rulesets.Edit; +using osuTK; + +namespace osu.Game.Skinning.Editor +{ + internal class SkinSettingsToolbox : ScrollingToolboxGroup + { + public const float WIDTH = 200; + + public SkinSettingsToolbox() + : base("Settings", 600) + { + RelativeSizeAxes = Axes.None; + Width = WIDTH; + + FillFlow.Spacing = new Vector2(10); + } + } +} From 458136dfe779b7daa83bbaf9c76bb0f37d14cc58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 19:55:25 +0900 Subject: [PATCH 087/339] Add `BigBlackBox` for skinning testing purposes --- osu.Game/Skinning/Components/BigBlackBox.cs | 47 +++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 osu.Game/Skinning/Components/BigBlackBox.cs diff --git a/osu.Game/Skinning/Components/BigBlackBox.cs b/osu.Game/Skinning/Components/BigBlackBox.cs new file mode 100644 index 0000000000..b86a863766 --- /dev/null +++ b/osu.Game/Skinning/Components/BigBlackBox.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 JetBrains.Annotations; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Skinning.Components +{ + /// + /// Intended to be a test bed for skinning. May be removed at some point in the future. + /// + [UsedImplicitly] + public class BigBlackBox : CompositeDrawable, ISkinnableDrawable + { + public bool UsesFixedAnchor { get; set; } + + public BigBlackBox() + { + Size = new Vector2(150); + + Masking = true; + CornerRadius = 20; + CornerExponent = 5; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + + new OsuSpriteText + { + Text = "Big Black Box", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + } + } +} From e4211104b0fb93ff266cae340b2fc02e54be24be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 20:33:15 +0900 Subject: [PATCH 088/339] Add new settings to the big black box --- osu.Game/Skinning/Components/BigBlackBox.cs | 34 +++++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/Components/BigBlackBox.cs b/osu.Game/Skinning/Components/BigBlackBox.cs index b86a863766..fc6c652343 100644 --- a/osu.Game/Skinning/Components/BigBlackBox.cs +++ b/osu.Game/Skinning/Components/BigBlackBox.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using JetBrains.Annotations; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -19,6 +21,19 @@ namespace osu.Game.Skinning.Components { public bool UsesFixedAnchor { get; set; } + [SettingSource("Spin", "Should the text spin")] + public Bindable TextSpin { get; } = new BindableBool(); + + [SettingSource("Alpha", "The alpha value of this box")] + public BindableNumber BoxAlpha { get; } = new BindableNumber(1) + { + MinValue = 0, + MaxValue = 1, + }; + + private readonly Box box; + private readonly OsuSpriteText text; + public BigBlackBox() { Size = new Vector2(150); @@ -29,13 +44,12 @@ namespace osu.Game.Skinning.Components InternalChildren = new Drawable[] { - new Box + box = new Box { Colour = Color4.Black, RelativeSizeAxes = Axes.Both, }, - - new OsuSpriteText + text = new OsuSpriteText { Text = "Big Black Box", Anchor = Anchor.Centre, @@ -43,5 +57,19 @@ namespace osu.Game.Skinning.Components } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + BoxAlpha.BindValueChanged(alpha => box.Alpha = alpha.NewValue, true); + TextSpin.BindValueChanged(spin => + { + if (spin.NewValue) + text.Spin(1000, RotationDirection.Clockwise); + else + text.ClearTransforms(); + }, true); + } } } From 7d2752185dfbf6261a240e35ca539afd1e902485 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Mar 2022 16:47:08 +0900 Subject: [PATCH 089/339] Add disclaimer and adjust metrics of `BigBlackBox` --- osu.Game/Skinning/Components/BigBlackBox.cs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Components/BigBlackBox.cs b/osu.Game/Skinning/Components/BigBlackBox.cs index fc6c652343..8e57143a12 100644 --- a/osu.Game/Skinning/Components/BigBlackBox.cs +++ b/osu.Game/Skinning/Components/BigBlackBox.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -21,7 +23,7 @@ namespace osu.Game.Skinning.Components { public bool UsesFixedAnchor { get; set; } - [SettingSource("Spin", "Should the text spin")] + [SettingSource("Spining text", "Whether the big text should spin")] public Bindable TextSpin { get; } = new BindableBool(); [SettingSource("Alpha", "The alpha value of this box")] @@ -29,14 +31,16 @@ namespace osu.Game.Skinning.Components { MinValue = 0, MaxValue = 1, + Precision = 0.01f, }; private readonly Box box; private readonly OsuSpriteText text; + private readonly OsuTextFlowContainer disclaimer; public BigBlackBox() { - Size = new Vector2(150); + Size = new Vector2(250); Masking = true; CornerRadius = 20; @@ -54,6 +58,17 @@ namespace osu.Game.Skinning.Components Text = "Big Black Box", Anchor = Anchor.Centre, Origin = Anchor.Centre, + Font = OsuFont.Default.With(size: 40) + }, + disclaimer = new OsuTextFlowContainer(st => st.Font = OsuFont.Default.With(size: 10)) + { + Text = "This is intended to be a test component and may disappear in the future!", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding(10), + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + TextAnchor = Anchor.TopCentre, } }; } @@ -70,6 +85,8 @@ namespace osu.Game.Skinning.Components else text.ClearTransforms(); }, true); + + disclaimer.FadeOutFromOne(5000, Easing.InQuint); } } } From 4a8aa72d1ba96dcb60f545250800ef71437a4cf3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 01:03:08 +0300 Subject: [PATCH 090/339] Nudge osu!taiko playfield's height to perfectly match with osu!(stable) --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index d650cab729..504b10e9bc 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// /// Default height of a when inside a . /// - public const float DEFAULT_HEIGHT = 212; + public const float DEFAULT_HEIGHT = 200; private Container hitExplosionContainer; private Container kiaiExplosionContainer; From dbc26c3534e37108497ae64e040ba32101988fa6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 03:46:05 +0300 Subject: [PATCH 091/339] Add failing test assertion --- .../Gameplay/TestSceneDrawableStoryboardSprite.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 52bedc328d..4011a6bc82 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -1,11 +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 System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Skinning; @@ -22,6 +24,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private Storyboard storyboard { get; set; } = new Storyboard(); + private IEnumerable sprites => this.ChildrenOfType(); + [Test] public void TestSkinSpriteDisallowedByDefault() { @@ -41,9 +45,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); - AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.Centre, Vector2.Zero))); + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); assertSpritesFromSkin(true); + + AddAssert("skinnable sprite has correct size", () => sprites.Any(s => Precision.AlmostEquals(s.ChildrenOfType().Single().Size, new Vector2(128, 128)))); } private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition) @@ -57,7 +63,6 @@ namespace osu.Game.Tests.Visual.Gameplay private void assertSpritesFromSkin(bool fromSkin) => AddAssert($"sprites are {(fromSkin ? "from skin" : "from storyboard")}", - () => this.ChildrenOfType() - .All(sprite => sprite.ChildrenOfType().Any() == fromSkin)); + () => sprites.All(sprite => sprite.ChildrenOfType().Any() == fromSkin)); } } From c9d54834be09429edc2a96a99ea579e15ac83870 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 03:35:08 +0300 Subject: [PATCH 092/339] Fix `SkinnableSprite`s in storyboards not autosizing to their textures --- osu.Game/Storyboards/Storyboard.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 2faed98ae0..b662b98e4e 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -104,7 +104,13 @@ namespace osu.Game.Storyboards drawable = new Sprite { Texture = textureStore.Get(storyboardPath) }; // if the texture isn't available locally in the beatmap, some storyboards choose to source from the underlying skin lookup hierarchy. else if (UseSkinSprites) - drawable = new SkinnableSprite(path); + { + drawable = new SkinnableSprite(path) + { + RelativeSizeAxes = Axes.None, + AutoSizeAxes = Axes.Both, + }; + } return drawable; } From 720e1cd206154fc40f8889ef813f1e31d01d1e0a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 03:35:23 +0300 Subject: [PATCH 093/339] Add failing test cases --- .../TestSceneDrawableStoryboardSprite.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 4011a6bc82..34e6d1996d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; @@ -52,6 +53,49 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("skinnable sprite has correct size", () => sprites.Any(s => Precision.AlmostEquals(s.ChildrenOfType().Single().Size, new Vector2(128, 128)))); } + [Test] + public void TestFlippedSprite() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + AddStep("flip sprites", () => sprites.ForEach(s => + { + s.FlipH = true; + s.FlipV = true; + })); + AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight)); + } + + [Test] + public void TestNegativeScale() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1))); + AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight)); + } + + [Test] + public void TestNegativeScaleWithFlippedSprite() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1))); + AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight)); + AddStep("flip sprites", () => sprites.ForEach(s => + { + s.FlipH = true; + s.FlipV = true; + })); + AddAssert("origin back", () => sprites.All(s => s.Origin == Anchor.TopLeft)); + } + private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition) => new DrawableStoryboardSprite( new StoryboardSprite(lookupName, origin, initialPosition) From 0b8c89bfa81c88019f629c8941d542f15d06fc6d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 03:50:12 +0300 Subject: [PATCH 094/339] Fix drawable storyboard sprites not flipping origin on negative scale --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index eb877f3dff..ad4402dcc4 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -79,7 +79,7 @@ namespace osu.Game.Storyboards.Drawables { var origin = base.Origin; - if (FlipH) + if ((FlipH || VectorScale.X < 0) && !(FlipH && VectorScale.X < 0)) { if (origin.HasFlagFast(Anchor.x0)) origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); @@ -87,7 +87,7 @@ namespace osu.Game.Storyboards.Drawables origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); } - if (FlipV) + if ((FlipV || VectorScale.Y < 0) && !(FlipV && VectorScale.Y < 0)) { if (origin.HasFlagFast(Anchor.y0)) origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); From 9cf05080da2bb0125eaf3483326d1ab4fa0ab66c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 04:40:24 +0300 Subject: [PATCH 095/339] Simplify conditionals to one XOR operations with comments --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index ad4402dcc4..f3173497e8 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -79,7 +79,8 @@ namespace osu.Game.Storyboards.Drawables { var origin = base.Origin; - if ((FlipH || VectorScale.X < 0) && !(FlipH && VectorScale.X < 0)) + // Either flip horizontally or negative X scale, but not both. + if (FlipH ^ (VectorScale.X < 0)) { if (origin.HasFlagFast(Anchor.x0)) origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); @@ -87,7 +88,8 @@ namespace osu.Game.Storyboards.Drawables origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); } - if ((FlipV || VectorScale.Y < 0) && !(FlipV && VectorScale.Y < 0)) + // Either flip vertically or negative Y scale, but not both. + if (FlipV ^ (VectorScale.Y < 0)) { if (origin.HasFlagFast(Anchor.y0)) origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); From 740a72e16d07b380177c331096780043ded35da1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 05:44:34 +0300 Subject: [PATCH 096/339] Share origin adjustment logic between storyboard sprite and animation --- .../Drawables/DrawableStoryboardAnimation.cs | 30 +------------ osu.Game/Storyboards/StoryboardExtensions.cs | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 28 deletions(-) create mode 100644 osu.Game/Storyboards/StoryboardExtensions.cs diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 81623a9307..28ac83a25c 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Textures; @@ -70,34 +69,9 @@ namespace osu.Game.Storyboards.Drawables public override bool RemoveWhenNotAlive => false; - protected override Vector2 DrawScale - => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; + protected override Vector2 DrawScale => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; - public override Anchor Origin - { - get - { - var origin = base.Origin; - - if (FlipH) - { - if (origin.HasFlagFast(Anchor.x0)) - origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - else if (origin.HasFlagFast(Anchor.x2)) - origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - } - - if (FlipV) - { - if (origin.HasFlagFast(Anchor.y0)) - origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - else if (origin.HasFlagFast(Anchor.y2)) - origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - } - - return origin; - } - } + public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV); public override bool IsPresent => !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent; diff --git a/osu.Game/Storyboards/StoryboardExtensions.cs b/osu.Game/Storyboards/StoryboardExtensions.cs new file mode 100644 index 0000000000..4e8251c9e7 --- /dev/null +++ b/osu.Game/Storyboards/StoryboardExtensions.cs @@ -0,0 +1,43 @@ +// 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.EnumExtensions; +using osu.Framework.Graphics; +using osuTK; + +namespace osu.Game.Storyboards +{ + public static class StoryboardExtensions + { + /// + /// Given an origin and a set of properties, adjust the origin to display the sprite/animation correctly. + /// + /// The current origin. + /// The vector scale. + /// Whether the element is flipped horizontally. + /// Whether the element is flipped vertically. + /// The adjusted origin. + public static Anchor AdjustOrigin(Anchor origin, Vector2 vectorScale, bool flipH, bool flipV) + { + // Either flip horizontally or negative X scale, but not both. + if (flipH ^ (vectorScale.X < 0)) + { + if (origin.HasFlagFast(Anchor.x0)) + origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + else if (origin.HasFlagFast(Anchor.x2)) + origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + } + + // Either flip vertically or negative Y scale, but not both. + if (flipV ^ (vectorScale.Y < 0)) + { + if (origin.HasFlagFast(Anchor.y0)) + origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + else if (origin.HasFlagFast(Anchor.y2)) + origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + } + + return origin; + } + } +} From c1697c7621358e02dccb710d79842696a983c669 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 06:29:49 +0300 Subject: [PATCH 097/339] Update `DrawableStoryboardSprite` to use helper method --- .../Drawables/DrawableStoryboardAnimation.cs | 3 +- .../Drawables/DrawableStoryboardSprite.cs | 29 +------------------ 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 28ac83a25c..88cb5f40a1 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -69,7 +69,8 @@ namespace osu.Game.Storyboards.Drawables public override bool RemoveWhenNotAlive => false; - protected override Vector2 DrawScale => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; + protected override Vector2 DrawScale + => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index f3173497e8..db10f13896 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; @@ -73,33 +72,7 @@ namespace osu.Game.Storyboards.Drawables protected override Vector2 DrawScale => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; - public override Anchor Origin - { - get - { - var origin = base.Origin; - - // Either flip horizontally or negative X scale, but not both. - if (FlipH ^ (VectorScale.X < 0)) - { - if (origin.HasFlagFast(Anchor.x0)) - origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - else if (origin.HasFlagFast(Anchor.x2)) - origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - } - - // Either flip vertically or negative Y scale, but not both. - if (FlipV ^ (VectorScale.Y < 0)) - { - if (origin.HasFlagFast(Anchor.y0)) - origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - else if (origin.HasFlagFast(Anchor.y2)) - origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - } - - return origin; - } - } + public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV); public override bool IsPresent => !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent; From 4ae6cba080d707402399b295f4a41b9503545991 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 13:49:38 +0900 Subject: [PATCH 098/339] Expose UseDevelopmentServer as virtual --- 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 8b75de9718..602f84b0e1 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -69,7 +69,7 @@ namespace osu.Game /// private const double global_track_volume_adjust = 0.8; - public bool UseDevelopmentServer { get; } + public virtual bool UseDevelopmentServer { get; } public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); From 21beb8774d5d3d95bbcd19e524e571e181512048 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 13:54:54 +0900 Subject: [PATCH 099/339] Change to lambda method --- osu.Game/OsuGameBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 602f84b0e1..5468db348e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -69,7 +69,7 @@ namespace osu.Game /// private const double global_track_volume_adjust = 0.8; - public virtual bool UseDevelopmentServer { get; } + public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild; public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); @@ -177,7 +177,6 @@ namespace osu.Game public OsuGameBase() { - UseDevelopmentServer = DebugUtils.IsDebugBuild; Name = @"osu!"; } From 4a3e3aba65a1cece80e164fefc7622f853637d8c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 14:25:26 +0900 Subject: [PATCH 100/339] Restructure PerformanceCalculator to not require ScoreInfo argument --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 3 +- .../Difficulty/CatchPerformanceCalculator.cs | 36 +++-- .../Difficulty/ManiaPerformanceCalculator.cs | 49 +++---- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Difficulty/OsuPerformanceCalculator.cs | 132 +++++++++--------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Difficulty/TaikoPerformanceCalculator.cs | 44 +++--- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 2 +- .../PerformanceBreakdownCalculator.cs | 2 +- .../Difficulty/PerformanceCalculator.cs | 36 ++--- osu.Game/Rulesets/Ruleset.cs | 18 +-- osu.Game/Scoring/ScorePerformanceCache.cs | 4 +- .../Play/HUD/PerformancePointsCounter.cs | 4 +- 14 files changed, 147 insertions(+), 189 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 70d11c42e5..3a7efb6263 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -19,7 +19,6 @@ using osu.Game.Rulesets.Catch.Difficulty; using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; using System; using osu.Framework.Extensions.EnumExtensions; using osu.Game.Rulesets.Catch.Edit; @@ -182,7 +181,7 @@ namespace osu.Game.Rulesets.Catch public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin); - public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score); + public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator(this); public int LegacyID => 2; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 8cdbe500f0..6fefb2e5bb 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -13,33 +13,29 @@ namespace osu.Game.Rulesets.Catch.Difficulty { public class CatchPerformanceCalculator : PerformanceCalculator { - protected new CatchDifficultyAttributes Attributes => (CatchDifficultyAttributes)base.Attributes; - - private Mod[] mods; - private int fruitsHit; private int ticksHit; private int tinyTicksHit; private int tinyTicksMissed; private int misses; - public CatchPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) - : base(ruleset, attributes, score) + public CatchPerformanceCalculator(Ruleset ruleset) + : base(ruleset) { } - public override PerformanceAttributes Calculate() + protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes) { - mods = Score.Mods; + var catchAttributes = (CatchDifficultyAttributes)attributes; - 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); + 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; + double value = Math.Pow(5.0 * Math.Max(1.0, catchAttributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0; // Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo int numTotalHits = totalComboHits(); @@ -52,10 +48,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty value *= Math.Pow(0.97, misses); // Combo scaling - if (Attributes.MaxCombo > 0) - value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); + if (catchAttributes.MaxCombo > 0) + value *= Math.Min(Math.Pow(score.MaxCombo, 0.8) / Math.Pow(catchAttributes.MaxCombo, 0.8), 1.0); - double approachRate = Attributes.ApproachRate; + double approachRate = catchAttributes.ApproachRate; double approachRateFactor = 1.0; if (approachRate > 9.0) approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9 @@ -66,7 +62,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty value *= approachRateFactor; - if (mods.Any(m => m is ModHidden)) + if (score.Mods.Any(m => m is ModHidden)) { // Hiddens gives almost nothing on max approach rate, and more the lower it is if (approachRate <= 10.0) @@ -75,12 +71,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty value *= 1.01 + 0.04 * (11.0 - Math.Min(11.0, approachRate)); // 5% at AR 10, 1% at AR 11 } - if (mods.Any(m => m is ModFlashlight)) + if (score.Mods.Any(m => m is ModFlashlight)) value *= 1.35 * lengthBonus; value *= Math.Pow(accuracy(), 5.5); - if (mods.Any(m => m is ModNoFail)) + if (score.Mods.Any(m => m is ModNoFail)) value *= 0.90; return new CatchPerformanceAttributes diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 722cb55036..1eaf45e54a 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -13,10 +13,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty { public class ManiaPerformanceCalculator : PerformanceCalculator { - protected new ManiaDifficultyAttributes Attributes => (ManiaDifficultyAttributes)base.Attributes; - - private Mod[] mods; - // Score after being scaled by non-difficulty-increasing mods private double scaledScore; @@ -27,39 +23,40 @@ namespace osu.Game.Rulesets.Mania.Difficulty private int countMeh; private int countMiss; - public ManiaPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) - : base(ruleset, attributes, score) + public ManiaPerformanceCalculator(Ruleset ruleset) + : base(ruleset) { } - public override PerformanceAttributes Calculate() + protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes) { - mods = Score.Mods; - scaledScore = Score.TotalScore; - 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); + var maniaAttributes = (ManiaDifficultyAttributes)attributes; - if (Attributes.ScoreMultiplier > 0) + scaledScore = score.TotalScore; + 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); + + if (maniaAttributes.ScoreMultiplier > 0) { // Scale score up, so it's comparable to other keymods - scaledScore *= 1.0 / Attributes.ScoreMultiplier; + scaledScore *= 1.0 / maniaAttributes.ScoreMultiplier; } // Arbitrary initial value for scaling pp in order to standardize distributions across game modes. // The specific number has no intrinsic meaning and can be adjusted as needed. double multiplier = 0.8; - if (mods.Any(m => m is ModNoFail)) + if (score.Mods.Any(m => m is ModNoFail)) multiplier *= 0.9; - if (mods.Any(m => m is ModEasy)) + if (score.Mods.Any(m => m is ModEasy)) multiplier *= 0.5; - double difficultyValue = computeDifficultyValue(); - double accValue = computeAccuracyValue(difficultyValue); + double difficultyValue = computeDifficultyValue(maniaAttributes); + double accValue = computeAccuracyValue(difficultyValue, maniaAttributes); double totalValue = Math.Pow( Math.Pow(difficultyValue, 1.1) + @@ -75,9 +72,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty }; } - private double computeDifficultyValue() + private double computeDifficultyValue(ManiaDifficultyAttributes attributes) { - double difficultyValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0; + double difficultyValue = Math.Pow(5 * Math.Max(1, attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0; difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0); @@ -97,14 +94,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty return difficultyValue; } - private double computeAccuracyValue(double difficultyValue) + private double computeAccuracyValue(double difficultyValue, ManiaDifficultyAttributes attributes) { - if (Attributes.GreatHitWindow <= 0) + if (attributes.GreatHitWindow <= 0) return 0; // Lots of arbitrary values from testing. // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution - double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667) + double accuracyValue = Math.Max(0.0, 0.2 - (attributes.GreatHitWindow - 34) * 0.006667) * difficultyValue * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index f139a88f50..d04ef69dd2 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this); - public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new ManiaPerformanceCalculator(this, attributes, score); + public override PerformanceCalculator CreatePerformanceCalculator() => new ManiaPerformanceCalculator(this); public const string SHORT_NAME = "mania"; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 604ab73454..5644b2009d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -14,10 +13,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuPerformanceCalculator : PerformanceCalculator { - public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes; - - private Mod[] mods; - private double accuracy; private int scoreMaxCombo; private int countGreat; @@ -27,31 +22,32 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double effectiveMissCount; - public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) - : base(ruleset, attributes, score) + public OsuPerformanceCalculator(Ruleset ruleset) + : base(ruleset) { } - public override PerformanceAttributes Calculate() + protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes) { - mods = Score.Mods; - accuracy = Score.Accuracy; - scoreMaxCombo = Score.MaxCombo; - countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great); - countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok); - countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); - countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); - effectiveMissCount = calculateEffectiveMissCount(); + var osuAttributes = (OsuDifficultyAttributes)attributes; + + accuracy = score.Accuracy; + scoreMaxCombo = score.MaxCombo; + countGreat = score.Statistics.GetValueOrDefault(HitResult.Great); + countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); + countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); + countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); + effectiveMissCount = calculateEffectiveMissCount(osuAttributes); double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. - if (mods.Any(m => m is OsuModNoFail)) + if (score.Mods.Any(m => m is OsuModNoFail)) multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount); - if (mods.Any(m => m is OsuModSpunOut) && totalHits > 0) - multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85); + if (score.Mods.Any(m => m is OsuModSpunOut) && totalHits > 0) + multiplier *= 1.0 - Math.Pow((double)osuAttributes.SpinnerCount / totalHits, 0.85); - if (mods.Any(h => h is OsuModRelax)) + if (score.Mods.Any(h => h is OsuModRelax)) { // As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it. effectiveMissCount = Math.Min(effectiveMissCount + countOk + countMeh, totalHits); @@ -59,10 +55,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty multiplier *= 0.6; } - double aimValue = computeAimValue(); - double speedValue = computeSpeedValue(); - double accuracyValue = computeAccuracyValue(); - double flashlightValue = computeFlashlightValue(); + double aimValue = computeAimValue(score, osuAttributes); + double speedValue = computeSpeedValue(score, osuAttributes); + double accuracyValue = computeAccuracyValue(score, osuAttributes); + double flashlightValue = computeFlashlightValue(score, osuAttributes); double totalValue = Math.Pow( Math.Pow(aimValue, 1.1) + @@ -82,11 +78,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty }; } - private double computeAimValue() + private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - double rawAim = Attributes.AimDifficulty; + double rawAim = attributes.AimDifficulty; - if (mods.Any(m => m is OsuModTouchDevice)) + if (score.Mods.Any(m => m is OsuModTouchDevice)) rawAim = Math.Pow(rawAim, 0.8); double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0; @@ -99,44 +95,44 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (effectiveMissCount > 0) aimValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), effectiveMissCount); - aimValue *= getComboScalingFactor(); + aimValue *= getComboScalingFactor(attributes); double approachRateFactor = 0.0; - if (Attributes.ApproachRate > 10.33) - approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33); - else if (Attributes.ApproachRate < 8.0) - approachRateFactor = 0.1 * (8.0 - Attributes.ApproachRate); + if (attributes.ApproachRate > 10.33) + approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); + else if (attributes.ApproachRate < 8.0) + approachRateFactor = 0.1 * (8.0 - attributes.ApproachRate); aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. - if (mods.Any(m => m is OsuModBlinds)) - aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * Attributes.DrainRate * Attributes.DrainRate); - else if (mods.Any(h => h is OsuModHidden)) + if (score.Mods.Any(m => m is OsuModBlinds)) + aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); + else if (score.Mods.Any(h => h is OsuModHidden)) { // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. - aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); + aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. - double estimateDifficultSliders = Attributes.SliderCount * 0.15; + double estimateDifficultSliders = attributes.SliderCount * 0.15; - if (Attributes.SliderCount > 0) + if (attributes.SliderCount > 0) { - double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); - double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor; + double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); + double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; aimValue *= sliderNerfFactor; } aimValue *= accuracy; // It is important to consider accuracy difficulty when scaling with accuracy. - aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; + aimValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; return aimValue; } - private double computeSpeedValue() + private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); @@ -146,27 +142,27 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (effectiveMissCount > 0) speedValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); - speedValue *= getComboScalingFactor(); + speedValue *= getComboScalingFactor(attributes); double approachRateFactor = 0.0; - if (Attributes.ApproachRate > 10.33) - approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33); + if (attributes.ApproachRate > 10.33) + approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); speedValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. - if (mods.Any(m => m is OsuModBlinds)) + if (score.Mods.Any(m => m is OsuModBlinds)) { // Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given. speedValue *= 1.12; } - else if (mods.Any(m => m is OsuModHidden)) + else if (score.Mods.Any(m => m is OsuModHidden)) { // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. - speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); + speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } // Scale the speed value with accuracy and OD. - speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2); + speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2); // Scale the speed value with # of 50s to punish doubletapping. speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0); @@ -174,14 +170,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty return speedValue; } - private double computeAccuracyValue() + private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - if (mods.Any(h => h is OsuModRelax)) + if (score.Mods.Any(h => h is OsuModRelax)) return 0.0; // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window. double betterAccuracyPercentage; - int amountHitObjectsWithAccuracy = Attributes.HitCircleCount; + int amountHitObjectsWithAccuracy = attributes.HitCircleCount; if (amountHitObjectsWithAccuracy > 0) betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); @@ -194,43 +190,43 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Lots of arbitrary values from testing. // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution. - double accuracyValue = Math.Pow(1.52163, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83; + double accuracyValue = Math.Pow(1.52163, attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83; // Bonus for many hitcircles - it's harder to keep good accuracy up for longer. accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3)); // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. - if (mods.Any(m => m is OsuModBlinds)) + if (score.Mods.Any(m => m is OsuModBlinds)) accuracyValue *= 1.14; - else if (mods.Any(m => m is OsuModHidden)) + else if (score.Mods.Any(m => m is OsuModHidden)) accuracyValue *= 1.08; - if (mods.Any(m => m is OsuModFlashlight)) + if (score.Mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02; return accuracyValue; } - private double computeFlashlightValue() + private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - if (!mods.Any(h => h is OsuModFlashlight)) + if (!score.Mods.Any(h => h is OsuModFlashlight)) return 0.0; - double rawFlashlight = Attributes.FlashlightDifficulty; + double rawFlashlight = attributes.FlashlightDifficulty; - if (mods.Any(m => m is OsuModTouchDevice)) + if (score.Mods.Any(m => m is OsuModTouchDevice)) rawFlashlight = Math.Pow(rawFlashlight, 0.8); double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0; - if (mods.Any(h => h is OsuModHidden)) + if (score.Mods.Any(h => h is OsuModHidden)) flashlightValue *= 1.3; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); - flashlightValue *= getComboScalingFactor(); + flashlightValue *= getComboScalingFactor(attributes); // Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius. flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, totalHits / 200.0) + @@ -239,19 +235,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Scale the flashlight value with accuracy _slightly_. flashlightValue *= 0.5 + accuracy / 2.0; // It is important to also consider accuracy difficulty when doing that. - flashlightValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; + flashlightValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; return flashlightValue; } - private double calculateEffectiveMissCount() + private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) { // Guess the number of misses + slider breaks from combo double comboBasedMissCount = 0.0; - if (Attributes.SliderCount > 0) + if (attributes.SliderCount > 0) { - double fullComboThreshold = Attributes.MaxCombo - 0.1 * Attributes.SliderCount; + double fullComboThreshold = attributes.MaxCombo - 0.1 * attributes.SliderCount; if (scoreMaxCombo < fullComboThreshold) comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); } @@ -262,7 +258,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty return Math.Max(countMiss, comboBasedMissCount); } - private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); + private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0); private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 5b936b1bf1..92b90339f9 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Osu public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(RulesetInfo, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new OsuPerformanceCalculator(this, attributes, score); + public override PerformanceCalculator CreatePerformanceCalculator() => new OsuPerformanceCalculator(this); public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index bcd55f8fae..9e73390fad 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -14,37 +14,35 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { public class TaikoPerformanceCalculator : PerformanceCalculator { - protected new TaikoDifficultyAttributes Attributes => (TaikoDifficultyAttributes)base.Attributes; - - private Mod[] mods; private int countGreat; private int countOk; private int countMeh; private int countMiss; - public TaikoPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) - : base(ruleset, attributes, score) + public TaikoPerformanceCalculator(Ruleset ruleset) + : base(ruleset) { } - public override PerformanceAttributes Calculate() + protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes) { - mods = Score.Mods; - countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great); - countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok); - countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); - countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); + var taikoAttributes = (TaikoDifficultyAttributes)attributes; + + countGreat = score.Statistics.GetValueOrDefault(HitResult.Great); + countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); + countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); + countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things - if (mods.Any(m => m is ModNoFail)) + if (score.Mods.Any(m => m is ModNoFail)) multiplier *= 0.90; - if (mods.Any(m => m is ModHidden)) + if (score.Mods.Any(m => m is ModHidden)) multiplier *= 1.10; - double difficultyValue = computeDifficultyValue(); - double accuracyValue = computeAccuracyValue(); + double difficultyValue = computeDifficultyValue(score, taikoAttributes); + double accuracyValue = computeAccuracyValue(score, taikoAttributes); double totalValue = Math.Pow( Math.Pow(difficultyValue, 1.1) + @@ -59,30 +57,30 @@ namespace osu.Game.Rulesets.Taiko.Difficulty }; } - private double computeDifficultyValue() + private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) { - double difficultyValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0; + double difficultyValue = Math.Pow(5.0 * Math.Max(1.0, attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0; double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0); difficultyValue *= lengthBonus; difficultyValue *= Math.Pow(0.985, countMiss); - if (mods.Any(m => m is ModHidden)) + if (score.Mods.Any(m => m is ModHidden)) difficultyValue *= 1.025; - if (mods.Any(m => m is ModFlashlight)) + if (score.Mods.Any(m => m is ModFlashlight)) difficultyValue *= 1.05 * lengthBonus; - return difficultyValue * Score.Accuracy; + return difficultyValue * score.Accuracy; } - private double computeAccuracyValue() + private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) { - if (Attributes.GreatHitWindow <= 0) + if (attributes.GreatHitWindow <= 0) return 0; - double accValue = Math.Pow(150.0 / Attributes.GreatHitWindow, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0; + double accValue = Math.Pow(150.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 15) * 22.0; // Bonus for many objects - it's harder to keep good accuracy up for longer return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index dc90845d92..8934b64ca5 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Taiko public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new TaikoPerformanceCalculator(this, attributes, score); + public override PerformanceCalculator CreatePerformanceCalculator() => new TaikoPerformanceCalculator(this); public int LegacyID => 1; diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index cc380df183..4eed2a25f5 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -390,7 +390,7 @@ namespace osu.Game.Tests.Visual.Ranking private class RulesetWithNoPerformanceCalculator : OsuRuleset { - public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null; + public override PerformanceCalculator CreatePerformanceCalculator() => null; } } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index fc38ed0298..1e5dda253f 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Difficulty ).ConfigureAwait(false); // ScorePerformanceCache is not used to avoid caching multiple copies of essentially identical perfect performance attributes - return difficulty == null ? null : ruleset.CreatePerformanceCalculator(difficulty.Value.Attributes, perfectPlay)?.Calculate(); + return difficulty == null ? null : ruleset.CreatePerformanceCalculator()?.Calculate(perfectPlay, difficulty.Value.Attributes); }, cancellationToken); } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs index 6358ec18b7..4c55249661 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs @@ -1,41 +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 System; -using System.Linq; -using osu.Framework.Audio.Track; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Rulesets.Mods; +using osu.Game.Beatmaps; using osu.Game.Scoring; namespace osu.Game.Rulesets.Difficulty { public abstract class PerformanceCalculator { - protected readonly DifficultyAttributes Attributes; - protected readonly Ruleset Ruleset; - protected readonly ScoreInfo Score; - protected double TimeRate { get; private set; } = 1; - - protected PerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) + protected PerformanceCalculator(Ruleset ruleset) { Ruleset = ruleset; - Score = score; - - Attributes = attributes ?? throw new ArgumentNullException(nameof(attributes)); - - ApplyMods(score.Mods); } - protected virtual void ApplyMods(Mod[] mods) - { - var track = new TrackVirtual(10000); - mods.OfType().ForEach(m => m.ApplyToTrack(track)); - TimeRate = track.Rate; - } + public PerformanceAttributes Calculate(ScoreInfo score, DifficultyAttributes attributes) + => CreatePerformanceAttributes(score, attributes); - public abstract PerformanceAttributes Calculate(); + public PerformanceAttributes Calculate(ScoreInfo score, IWorkingBeatmap beatmap) + => Calculate(score, Ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods)); + + /// + /// Creates to describe a score's performance. + /// + /// The score to create the attributes for. + /// The difficulty attributes for the beatmap relating to the score. + protected abstract PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes); } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 616540b59c..d5926386e8 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -228,25 +228,9 @@ namespace osu.Game.Rulesets /// /// Optionally creates a to generate performance data from the provided score. /// - /// Difficulty attributes for the beatmap related to the provided score. - /// The score to be processed. /// A performance calculator instance for the provided score. [CanBeNull] - public virtual PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null; - - /// - /// Optionally creates a to generate performance data from the provided score. - /// - /// The beatmap to use as a source for generating . - /// The score to be processed. - /// A performance calculator instance for the provided score. - [CanBeNull] - public PerformanceCalculator CreatePerformanceCalculator(IWorkingBeatmap beatmap, ScoreInfo score) - { - var difficultyCalculator = CreateDifficultyCalculator(beatmap); - var difficultyAttributes = difficultyCalculator.Calculate(score.Mods); - return CreatePerformanceCalculator(difficultyAttributes, score); - } + public virtual PerformanceCalculator CreatePerformanceCalculator() => null; public virtual HitObjectComposer CreateHitObjectComposer() => null; diff --git a/osu.Game/Scoring/ScorePerformanceCache.cs b/osu.Game/Scoring/ScorePerformanceCache.cs index a428a66aae..e15d59e648 100644 --- a/osu.Game/Scoring/ScorePerformanceCache.cs +++ b/osu.Game/Scoring/ScorePerformanceCache.cs @@ -43,9 +43,7 @@ namespace osu.Game.Scoring token.ThrowIfCancellationRequested(); - var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Value.Attributes, score); - - return calculator?.Calculate(); + return score.Ruleset.CreateInstance().CreatePerformanceCalculator()?.Calculate(score, attributes.Value.Attributes); } public readonly struct PerformanceCacheLookup diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 21a7698248..67a8aaabdc 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -130,9 +130,9 @@ namespace osu.Game.Screens.Play.HUD var scoreInfo = gameplayState.Score.ScoreInfo.DeepClone(); scoreInfo.Mods = clonedMods; - var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(attrib, scoreInfo); + var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(); - Current.Value = (int)Math.Round(calculator?.Calculate().Total ?? 0, MidpointRounding.AwayFromZero); + Current.Value = (int)Math.Round(calculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero); IsValid = true; } From 926827207abc22efc5819b168a6ed94ea91562d8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 14:44:04 +0900 Subject: [PATCH 101/339] Reduce calculator allocations in counter --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 67a8aaabdc..a9b5544921 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -55,6 +55,7 @@ namespace osu.Game.Screens.Play.HUD private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); private JudgementResult lastJudgement; + private PerformanceCalculator performanceCalculator; public PerformancePointsCounter() { @@ -70,6 +71,8 @@ namespace osu.Game.Screens.Play.HUD if (gameplayState != null) { + performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator(); + clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray(); var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); @@ -130,9 +133,7 @@ namespace osu.Game.Screens.Play.HUD var scoreInfo = gameplayState.Score.ScoreInfo.DeepClone(); scoreInfo.Mods = clonedMods; - var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(); - - Current.Value = (int)Math.Round(calculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero); + Current.Value = (int)Math.Round(performanceCalculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero); IsValid = true; } From 9cc7f70872a00d15c91836114fda6b72781eb652 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 15:38:00 +0900 Subject: [PATCH 102/339] Nullable annotate classes --- osu.Game/Rulesets/Scoring/HealthProcessor.cs | 6 ++++-- osu.Game/Rulesets/Scoring/JudgementProcessor.cs | 10 ++++++---- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 ++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs index 1535fe4d00..a92c30e593 100644 --- a/osu.Game/Rulesets/Scoring/HealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/HealthProcessor.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.Utils; @@ -14,12 +16,12 @@ namespace osu.Game.Rulesets.Scoring /// Invoked when the is in a failed state. /// Return true if the fail was permitted. /// - public event Func Failed; + public event Func? Failed; /// /// Additional conditions on top of that cause a failing state. /// - public event Func FailConditions; + public event Func? FailConditions; /// /// The current health. diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index a643c31daa..3017841c9d 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.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.Extensions.TypeExtensions; @@ -17,12 +19,12 @@ namespace osu.Game.Rulesets.Scoring /// /// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by this . /// - public event Action NewJudgement; + public event Action? NewJudgement; /// /// Invoked when a judgement is reverted, usually due to rewinding gameplay. /// - public event Action JudgementReverted; + public event Action? JudgementReverted; /// /// The maximum number of hits that can be judged. @@ -34,7 +36,7 @@ namespace osu.Game.Rulesets.Scoring /// public int JudgedHits { get; private set; } - private JudgementResult lastAppliedResult; + private JudgementResult? lastAppliedResult; private readonly BindableBool hasCompleted = new BindableBool(); @@ -163,7 +165,7 @@ namespace osu.Game.Rulesets.Scoring protected override void Update() { base.Update(); - hasCompleted.Value = JudgedHits == MaxHits && (JudgedHits == 0 || lastAppliedResult.TimeAbsolute < Clock.CurrentTime); + hasCompleted.Value = JudgedHits == MaxHits && (JudgedHits == 0 || lastAppliedResult?.TimeAbsolute < Clock.CurrentTime); } /// diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index db53e3130c..a7fe8b354b 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.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 System.Collections.Generic; using System.Diagnostics; @@ -24,7 +26,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Invoked when this was reset from a replay frame. /// - public event Action OnResetFromReplayFrame; + public event Action? OnResetFromReplayFrame; /// /// The current total score. @@ -110,7 +112,7 @@ namespace osu.Game.Rulesets.Scoring private readonly Dictionary scoreResultCounts = new Dictionary(); private readonly List hitEvents = new List(); - private HitObject lastHitObject; + private HitObject? lastHitObject; private double scoreMultiplier = 1; From 3fff7f4b7e9a5921be5928752840e59531090de4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 15:51:10 +0900 Subject: [PATCH 103/339] Require ScoreProcessor to receive ruleset --- .../Scoring/PippidonScoreProcessor.cs | 11 ----------- .../TestSceneComboCounter.cs | 2 +- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../Scoring/CatchScoreProcessor.cs | 5 +++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Scoring/ManiaScoreProcessor.cs | 5 +++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Scoring/OsuScoreProcessor.cs | 5 +++++ .../Skinning/TestSceneDrawableTaikoMascot.cs | 2 +- .../Scoring/TaikoScoreProcessor.cs | 5 +++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../Gameplay/TestSceneScoreProcessor.cs | 6 +++--- .../Rulesets/Scoring/ScoreProcessorTest.cs | 3 ++- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 2 +- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 3 ++- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 5 +++++ .../TestScenePerformancePointsCounter.cs | 2 +- .../TestSceneSkinEditorMultipleSkins.cs | 2 +- .../TestSceneSkinnableAccuracyCounter.cs | 3 ++- .../Gameplay/TestSceneSkinnableComboCounter.cs | 3 ++- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 2 +- .../Gameplay/TestSceneSkinnableScoreCounter.cs | 3 ++- .../Gameplay/TestSceneUnstableRateCounter.cs | 6 ++++++ .../TestSceneMultiSpectatorLeaderboard.cs | 2 +- .../TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- ...SceneMultiplayerGameplayLeaderboardTeams.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 11 +++++++---- .../Play/HUD/PerformancePointsCounter.cs | 12 ++++++------ .../Skinning/Editor/SkinComponentToolbox.cs | 18 +++++++++++++++++- 30 files changed, 88 insertions(+), 44 deletions(-) delete mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.cs diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.cs deleted file mode 100644 index 1c4fe698c2..0000000000 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.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. - -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Pippidon.Scoring -{ - public class PippidonScoreProcessor : ScoreProcessor - { - } -} diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs index 064a84cb98..b720ab1e97 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests [SetUp] public void SetUp() => Schedule(() => { - scoreProcessor = new ScoreProcessor(); + scoreProcessor = new ScoreProcessor(new CatchRuleset()); SetContents(_ => new CatchComboDisplay { diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 3a7efb6263..45198fa0ca 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(); + public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this); diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 9cd03dc869..2c183f5d94 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -7,6 +7,11 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { + public CatchScoreProcessor(Ruleset ruleset) + : base(ruleset) + { + } + protected override double ClassicScoreMultiplier => 28; } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index d04ef69dd2..92c77f405b 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); + public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new ManiaHealthProcessor(drainStartTime, 0.5); diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index e0b19d87e8..0ffee809fc 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -7,6 +7,11 @@ namespace osu.Game.Rulesets.Mania.Scoring { internal class ManiaScoreProcessor : ScoreProcessor { + public ManiaScoreProcessor(Ruleset ruleset) + : base(ruleset) + { + } + protected override double DefaultAccuracyPortion => 0.99; protected override double DefaultComboPortion => 0.01; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 92b90339f9..87ffe8983f 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); + public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index df38f0204a..e397fea0ed 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -11,6 +11,11 @@ namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { + public OsuScoreProcessor(Ruleset ruleset) + : base(ruleset) + { + } + protected override double ClassicScoreMultiplier => 36; protected override HitEvent CreateHitEvent(JudgementResult result) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index 920a7cd1a1..b7e1c9586a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [SetUp] public void SetUp() { - scoreProcessor = new TaikoScoreProcessor(); + scoreProcessor = new TaikoScoreProcessor(new TaikoRuleset()); } [Test] diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 849b9c14bd..ddf6587022 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -7,6 +7,11 @@ namespace osu.Game.Rulesets.Taiko.Scoring { internal class TaikoScoreProcessor : ScoreProcessor { + public TaikoScoreProcessor(Ruleset ruleset) + : base(ruleset) + { + } + protected override double DefaultAccuracyPortion => 0.75; protected override double DefaultComboPortion => 0.25; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 8934b64ca5..a3a2858bd5 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(); + public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new TaikoHealthProcessor(); diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 70ba868de6..9c307341bd 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Gameplay { var beatmap = new Beatmap { HitObjects = { new HitObject() } }; - var scoreProcessor = new ScoreProcessor(); + var scoreProcessor = new ScoreProcessor(new OsuRuleset()); scoreProcessor.ApplyBeatmap(beatmap); // Apply a miss judgement @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Gameplay { var beatmap = new Beatmap { HitObjects = { new HitObject() } }; - var scoreProcessor = new ScoreProcessor(); + var scoreProcessor = new ScoreProcessor(new OsuRuleset()); scoreProcessor.ApplyBeatmap(beatmap); // Apply a judgement @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Gameplay { var beatmap = new Beatmap { HitObjects = { new HitCircle() } }; - var scoreProcessor = new ScoreProcessor(); + var scoreProcessor = new ScoreProcessor(new OsuRuleset()); scoreProcessor.ApplyBeatmap(beatmap); scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great }); diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 2a19d51c9d..5c01950bea 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -27,7 +28,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [SetUp] public void SetUp() { - scoreProcessor = new ScoreProcessor(); + scoreProcessor = new ScoreProcessor(new OsuRuleset()); beatmap = new TestBeatmap(new RulesetInfo()) { HitObjects = new List diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index c5f56cae9e..c5ea9e6204 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay private SkinManager skinManager { get; set; } [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(); + private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 4b54cd3510..505f73159f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -24,7 +25,7 @@ namespace osu.Game.Tests.Visual.Gameplay private HUDOverlay hudOverlay; [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(); + private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index c1260f0231..ab9758a2ee 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -273,6 +273,11 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestScoreProcessor : ScoreProcessor { + public TestScoreProcessor() + : base(new OsuRuleset()) + { + } + public void Reset() => base.Reset(false); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs index 84c7f611af..aefe0db36a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay gameplayState = new GameplayState(beatmap, ruleset); gameplayState.LastJudgementResult.BindTo(lastJudgementResult); - scoreProcessor = new ScoreProcessor(); + scoreProcessor = new ScoreProcessor(ruleset); Child = dependencyContainer = new DependencyProvidingContainer { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 856747ad19..8f33f6fac5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSceneSkinEditorMultipleSkins : SkinnableTestScene { [Cached] - private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(); + private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs index 80eb887894..9c713b4616 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; @@ -14,7 +15,7 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSceneSkinnableAccuracyCounter : SkinnableHUDComponentTestScene { [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(); + private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); protected override Drawable CreateDefaultImplementation() => new DefaultAccuracyCounter(); protected override Drawable CreateLegacyImplementation() => new LegacyAccuracyCounter(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs index bd1fe050af..f507172931 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -13,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSceneSkinnableComboCounter : SkinnableHUDComponentTestScene { [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(); + private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter(); protected override Drawable CreateLegacyImplementation() => new LegacyComboCounter(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 3074a91dc6..cdf349ff7f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay private HUDOverlay hudOverlay; [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(); + private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs index 1700886263..a871e37ad4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; @@ -13,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSceneSkinnableScoreCounter : SkinnableHUDComponentTestScene { [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(); + private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); protected override Drawable CreateDefaultImplementation() => new DefaultScoreCounter(); protected override Drawable CreateLegacyImplementation() => new LegacyScoreCounter(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs index be20799479..ca8ecd490d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; @@ -102,6 +103,11 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestScoreProcessor : ScoreProcessor { + public TestScoreProcessor() + : base(new OsuRuleset()) + { + } + public void Reset() => base.Reset(false); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index f57a54d84c..0cc1257a07 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); - var scoreProcessor = new OsuScoreProcessor(); + var scoreProcessor = new OsuScoreProcessor(Ruleset.Value.CreateInstance()); scoreProcessor.ApplyBeatmap(playable); LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index bcd4474876..0edc56f5d3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Children = new Drawable[] { - scoreProcessor = new OsuScoreProcessor(), + scoreProcessor = new OsuScoreProcessor(Ruleset.Value.CreateInstance()), }; scoreProcessor.ApplyBeatmap(playableBeatmap); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index 7f5aced925..60a5785b31 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Children = new Drawable[] { - scoreProcessor = new OsuScoreProcessor(), + scoreProcessor = new OsuScoreProcessor(Ruleset.Value.CreateInstance()), }; scoreProcessor.ApplyBeatmap(playableBeatmap); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index d5926386e8..b7c6132bdb 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -201,7 +201,7 @@ namespace osu.Game.Rulesets /// Creates a for this . /// /// The score processor. - public virtual ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(); + public virtual ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); /// /// Creates a for this . diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index a7fe8b354b..b982c6ece2 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -84,6 +84,7 @@ namespace osu.Game.Rulesets.Scoring /// protected virtual double ClassicScoreMultiplier => 36; + private readonly Ruleset ruleset; private readonly double accuracyPortion; private readonly double comboPortion; @@ -116,8 +117,10 @@ namespace osu.Game.Rulesets.Scoring private double scoreMultiplier = 1; - public ScoreProcessor() + public ScoreProcessor(Ruleset ruleset) { + this.ruleset = ruleset; + accuracyPortion = DefaultAccuracyPortion; comboPortion = DefaultComboPortion; @@ -255,7 +258,7 @@ namespace osu.Game.Rulesets.Scoring /// The total score in the given . public double ComputeFinalScore(ScoringMode mode, ScoreInfo scoreInfo) { - extractFromStatistics(scoreInfo.Ruleset.CreateInstance(), + extractFromStatistics(ruleset, scoreInfo.Statistics, out double extractedBaseScore, out double extractedMaxBaseScore, @@ -282,7 +285,7 @@ namespace osu.Game.Rulesets.Scoring if (!beatmapApplied) throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}."); - extractFromStatistics(scoreInfo.Ruleset.CreateInstance(), + extractFromStatistics(ruleset, scoreInfo.Statistics, out double extractedBaseScore, out _, @@ -317,7 +320,7 @@ namespace osu.Game.Rulesets.Scoring if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3) { extractFromStatistics( - scoreInfo.Ruleset.CreateInstance(), + ruleset, scoreInfo.Statistics, out double computedBaseScore, out double computedMaxBaseScore, diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index a9b5544921..af360b42bb 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -26,6 +26,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Skinning; using osuTK; @@ -56,6 +57,7 @@ namespace osu.Game.Screens.Play.HUD private JudgementResult lastJudgement; private PerformanceCalculator performanceCalculator; + private ScoreInfo scoreInfo; public PerformancePointsCounter() { @@ -72,9 +74,10 @@ namespace osu.Game.Screens.Play.HUD if (gameplayState != null) { performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator(); - clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray(); + scoreInfo = new ScoreInfo(gameplayState.Score.ScoreInfo.BeatmapInfo, gameplayState.Score.ScoreInfo.Ruleset); + var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) .ContinueWith(task => Schedule(() => @@ -123,16 +126,13 @@ namespace osu.Game.Screens.Play.HUD var attrib = getAttributeAtTime(judgement); - if (gameplayState == null || attrib == null) + if (gameplayState == null || attrib == null || scoreProcessor == null) { IsValid = false; return; } - // awkward but we need to make sure the true mods are not passed to PerformanceCalculator as it makes a mess of track applications. - var scoreInfo = gameplayState.Score.ScoreInfo.DeepClone(); - scoreInfo.Mods = clonedMods; - + scoreProcessor.PopulateScore(scoreInfo); Current.Value = (int)Math.Round(performanceCalculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero); IsValid = true; } diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index ce9afd650a..a9dcf705a9 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.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.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -11,11 +12,16 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; using osuTK; using osuTK.Graphics; @@ -30,7 +36,7 @@ namespace osu.Game.Skinning.Editor private const float component_display_scale = 0.8f; [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor + private ScoreProcessor scoreProcessor = new ScoreProcessor(new DummyRuleset()) { Combo = { Value = RNG.Next(1, 1000) }, TotalScore = { Value = RNG.Next(1000, 10000000) } @@ -171,5 +177,15 @@ namespace osu.Game.Skinning.Editor return true; } } + + private class DummyRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException(); + public override string Description { get; } + public override string ShortName { get; } + } } } From 028750936c1243f0da0627416cddc067154238e2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 17:10:37 +0900 Subject: [PATCH 104/339] Apply review suggestions --- osu.Game/Rulesets/Scoring/JudgementProcessor.cs | 8 +++++++- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index 3017841c9d..94ddc32bb7 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Game.Beatmaps; @@ -165,7 +166,12 @@ namespace osu.Game.Rulesets.Scoring protected override void Update() { base.Update(); - hasCompleted.Value = JudgedHits == MaxHits && (JudgedHits == 0 || lastAppliedResult?.TimeAbsolute < Clock.CurrentTime); + + hasCompleted.Value = + JudgedHits == MaxHits + && (JudgedHits == 0 + // Last applied result is guaranteed to be non-null when JudgedHits > 0. + || lastAppliedResult.AsNonNull().TimeAbsolute < Clock.CurrentTime); } /// diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index af360b42bb..c0d0ea0721 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Play.HUD performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator(); clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray(); - scoreInfo = new ScoreInfo(gameplayState.Score.ScoreInfo.BeatmapInfo, gameplayState.Score.ScoreInfo.Ruleset); + scoreInfo = new ScoreInfo(gameplayState.Score.ScoreInfo.BeatmapInfo, gameplayState.Score.ScoreInfo.Ruleset) { Mods = clonedMods }; var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) From 8676a2587c12c2a200d6122d53736a611401e1a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 15:45:43 +0900 Subject: [PATCH 105/339] Add the ability for `HitObject`s to specify auxiliary samples --- osu.Game/Rulesets/Objects/HitObject.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index c590cc302f..57b897e5b5 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using JetBrains.Annotations; using Newtonsoft.Json; @@ -67,6 +68,12 @@ namespace osu.Game.Rulesets.Objects } } + /// + /// Any samples which may be used by this hit object that are non-standard. + /// This is used only to preload these samples ahead of time. + /// + public virtual IList AuxiliarySamples => ImmutableList.Empty; + public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT; public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; From 90e34d7686b9a9762ba153ca841c5ad685497f3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 15:45:57 +0900 Subject: [PATCH 106/339] Move slider slide samples to auxiliary specification --- .../Objects/Drawables/DrawableSlider.cs | 22 +++++-------------- osu.Game.Rulesets.Osu/Objects/Slider.cs | 17 ++++++++++++++ 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 3acec4498d..1447f131c6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -2,23 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; -using osuTK; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; -using osuTK.Graphics; using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -126,18 +125,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } Samples.Samples = HitObject.TailSamples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); - - var slidingSamples = new List(); - - var normalSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); - if (normalSample != null) - slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(normalSample).With("sliderslide")); - - var whistleSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); - if (whistleSample != null) - slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(whistleSample).With("sliderwhistle")); - - slidingSample.Samples = slidingSamples.ToArray(); + slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); } public override void StopAllSamples() diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 5c1c3fd253..601311694d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -29,6 +29,23 @@ namespace osu.Game.Rulesets.Osu.Objects set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } + public override IList AuxiliarySamples => CreateSlidingSamples(); + + public IList CreateSlidingSamples() + { + var slidingSamples = new List(); + + var normalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); + if (normalSample != null) + slidingSamples.Add(normalSample.With("sliderslide")); + + var whistleSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); + if (whistleSample != null) + slidingSamples.Add(whistleSample.With("sliderwhistle")); + + return slidingSamples; + } + private readonly Cached endPositionCache = new Cached(); public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); From be9920218805cc3280fb1e16d3816774305ca069 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:08:04 +0900 Subject: [PATCH 107/339] Move spinner spin samples to auxiliary specification --- .../Objects/Drawables/DrawableSpinner.cs | 11 ++--------- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index c6db02ee02..a904658a4c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -121,15 +121,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.LoadSamples(); - var firstSample = HitObject.Samples.FirstOrDefault(); - - if (firstSample != null) - { - var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("spinnerspin"); - - spinningSample.Samples = new ISampleInfo[] { clone }; - spinningSample.Frequency.Value = spinning_sample_initial_frequency; - } + spinningSample.Samples = HitObject.CreateSpinningSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); + spinningSample.Frequency.Value = spinning_sample_initial_frequency; } private void updateSpinningSample(ValueChangedEvent tracking) diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 1eddfb7fef..ddee4d3ebd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -1,7 +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 System.Threading; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; @@ -73,5 +77,20 @@ namespace osu.Game.Rulesets.Osu.Objects public override Judgement CreateJudgement() => new OsuJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; + + public override IList AuxiliarySamples => CreateSpinningSamples(); + + public HitSampleInfo[] CreateSpinningSamples() + { + var referenceSample = Samples.FirstOrDefault(); + + if (referenceSample == null) + return Array.Empty(); + + return new[] + { + SampleControlPoint.ApplyTo(referenceSample).With("spinnerspin") + }; + } } } From 6d6f73e0168d6c7d5c447e0b32c6d8566d5b4761 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:08:26 +0900 Subject: [PATCH 108/339] Add overrides in `DrawableSliderTail` to explain/warn that this class never plays its own samples --- .../Objects/Drawables/DrawableSliderTail.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index ec1387eb54..64964ed396 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -74,6 +74,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); } + protected override void LoadSamples() + { + // Tail models don't actually get samples, as the playback is handled by DrawableSlider. + // This override is only here for visibility in explaining this weird flow. + } + + public override void PlaySamples() + { + // Tail models don't actually get samples, as the playback is handled by DrawableSlider. + // This override is only here for visibility in explaining this weird flow. + } + protected override void UpdateInitialTransforms() { base.UpdateInitialTransforms(); From 45233932089ea26d9e329c47f1893137d2d4bcaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:08:57 +0900 Subject: [PATCH 109/339] Remove `IsLayered` from `LegacyHitSampleInfo` comparison The equality of samples is generally used to compare the sample equality, not its full properties. For instance, we don't compare `Volume` in the base implementation. Having `IsLayered` here breaks actual usages of equality, ie. for pooling purposes. --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index b091803406..47fe9032f0 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -500,7 +500,7 @@ namespace osu.Game.Rulesets.Objects.Legacy => new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered)); public bool Equals(LegacyHitSampleInfo? other) - => base.Equals(other) && CustomSampleBank == other.CustomSampleBank && IsLayered == other.IsLayered; + => base.Equals(other) && CustomSampleBank == other.CustomSampleBank; public override bool Equals(object? obj) => obj is LegacyHitSampleInfo other && Equals(other); From 1b8c632b878b6d5c30a7a5693dc5834d0aa987b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:17:14 +0900 Subject: [PATCH 110/339] Add `TailSamples` to auxiliary samples list --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 601311694d..776165cfb4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public override IList AuxiliarySamples => CreateSlidingSamples(); + public override IList AuxiliarySamples => CreateSlidingSamples().Concat(TailSamples).ToArray(); public IList CreateSlidingSamples() { From 39d95aa8cf412b1a5ef66e1cd1d05541fef4f3bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:19:23 +0900 Subject: [PATCH 111/339] Add automatic preloading of sample pools at a `Playfield` level --- osu.Game/Rulesets/UI/Playfield.cs | 73 +++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 30e71dde1c..cd8f99db8b 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -3,22 +3,22 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; -using System.Diagnostics; namespace osu.Game.Rulesets.UI { @@ -264,10 +264,25 @@ namespace osu.Game.Rulesets.UI var entry = CreateLifetimeEntry(hitObject); lifetimeEntryMap[entry.HitObject] = entry; + preloadSamples(hitObject); + HitObjectContainer.Add(entry); OnHitObjectAdded(entry.HitObject); } + private void preloadSamples(HitObject hitObject) + { + // prepare sample pools ahead of time so we're not initialising at runtime. + foreach (var sample in hitObject.Samples) + prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample)); + + foreach (var sample in hitObject.AuxiliarySamples) + prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample)); + + foreach (var nestedObject in hitObject.NestedHitObjects) + preloadSamples(nestedObject); + } + /// /// Removes a for a pooled from this . /// @@ -330,22 +345,7 @@ namespace osu.Game.Rulesets.UI DrawableHitObject IPooledHitObjectProvider.GetPooledDrawableRepresentation(HitObject hitObject, DrawableHitObject parent) { - var lookupType = hitObject.GetType(); - - IDrawablePool pool; - - // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists. - if (!pools.TryGetValue(lookupType, out pool)) - { - foreach (var (t, p) in pools) - { - if (!t.IsInstanceOfType(hitObject)) - continue; - - pools[lookupType] = pool = p; - break; - } - } + var pool = prepareDrawableHitObjectPool(hitObject); return (DrawableHitObject)pool?.Get(d => { @@ -372,14 +372,39 @@ namespace osu.Game.Rulesets.UI }); } + private IDrawablePool prepareDrawableHitObjectPool(HitObject hitObject) + { + var lookupType = hitObject.GetType(); + + IDrawablePool pool; + + // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists. + if (!pools.TryGetValue(lookupType, out pool)) + { + foreach (var (t, p) in pools) + { + if (!t.IsInstanceOfType(hitObject)) + continue; + + pools[lookupType] = pool = p; + break; + } + } + + return pool; + } + private readonly Dictionary> samplePools = new Dictionary>(); - public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo) - { - if (!samplePools.TryGetValue(sampleInfo, out var existingPool)) - AddInternal(samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 1)); + public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo) => prepareSamplePool(sampleInfo).Get(); - return existingPool.Get(); + private DrawablePool prepareSamplePool(ISampleInfo sampleInfo) + { + if (samplePools.TryGetValue(sampleInfo, out var pool)) return pool; + + AddInternal(samplePools[sampleInfo] = pool = new DrawableSamplePool(sampleInfo, 1)); + + return pool; } private class DrawableSamplePool : DrawablePool From 3c5fda5f23fce4a384cf286b4cab2ff180cfaa33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:24:31 +0900 Subject: [PATCH 112/339] Add early exist if the target screen is no longer current --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index abd8272633..73af2591c8 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -132,6 +132,9 @@ namespace osu.Game.Skinning.Editor { Debug.Assert(skinEditor != null); + if (!target.IsCurrentScreen()) + return; + if (!target.IsLoaded) { Scheduler.AddOnce(setTarget, target); From 16ee6b5fc7700b2a46359fb6b814e9c56eafaa53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 18:12:04 +0900 Subject: [PATCH 113/339] Remove `IsLayered` from `GetHasCode` implementation --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 47fe9032f0..8dc037c7c8 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -505,7 +505,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public override bool Equals(object? obj) => obj is LegacyHitSampleInfo other && Equals(other); - public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank, IsLayered); + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank); } private class FileHitSampleInfo : LegacyHitSampleInfo, IEquatable From b31ff679fb1af632f8b40d980a3d3528a08e43a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 19:16:19 +0900 Subject: [PATCH 114/339] Provide correct `HitResult` type for random judgements in `TestSceneHitErrorMeter` --- osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index c1260f0231..a93ab6810d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -48,7 +48,11 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create display", () => recreateDisplay(new OsuHitWindows(), 5)); - AddRepeatStep("New random judgement", () => newJudgement(), 40); + AddRepeatStep("New random judgement", () => + { + double offset = RNG.Next(-150, 150); + newJudgement(offset, drawableRuleset.HitWindows.ResultFor(offset)); + }, 400); AddRepeatStep("New max negative", () => newJudgement(-drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20); AddRepeatStep("New max positive", () => newJudgement(drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20); From 6eed2c35a4cb79877ced63626745d8768fb196a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 19:16:38 +0900 Subject: [PATCH 115/339] Adjust visual appearance of `BarHitErrorMeter` for easier reading --- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 157 ++++++++++-------- 1 file changed, 89 insertions(+), 68 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 7903e54960..2f9206e0fb 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD.HitErrorMeters { @@ -21,14 +20,15 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { private const int arrow_move_duration = 400; - private const int judgement_line_width = 6; + private const int judgement_line_width = 10; + private const int judgement_line_height = 3; + + private const int centre_marker_size = 6; private const int bar_height = 200; private const int bar_width = 2; - private const int spacing = 2; - private const float chevron_size = 8; private SpriteIcon arrow; @@ -50,54 +50,25 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [BackgroundDependencyLoader] private void load() { - InternalChild = new FillFlowContainer + var hitWindows = HitWindows.GetAllAvailableWindows().ToArray(); + + InternalChild = new Container { AutoSizeAxes = Axes.X, Height = bar_height, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(spacing, 0), Margin = new MarginPadding(2), Children = new Drawable[] { - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = chevron_size, - RelativeSizeAxes = Axes.Y, - Child = arrow = new SpriteIcon - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Y = 0.5f, - Icon = FontAwesome.Solid.ChevronRight, - Size = new Vector2(chevron_size), - } - }, colourBars = new Container { - Width = bar_width, - RelativeSizeAxes = Axes.Y, + Name = "colour axis", + X = chevron_size, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Width = judgement_line_width, + RelativeSizeAxes = Axes.Y, Children = new Drawable[] { - colourBarsEarly = new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Scale = new Vector2(1, -1), - }, - colourBarsLate = new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - }, iconEarly = new SpriteIcon { Y = -10, @@ -113,20 +84,72 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Icon = FontAwesome.Solid.Bicycle, Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, - } + }, + colourBarsEarly = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Width = bar_width, + RelativeSizeAxes = Axes.Y, + Height = 0.5f, + Scale = new Vector2(1, -1), + }, + colourBarsLate = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Width = bar_width, + RelativeSizeAxes = Axes.Y, + Height = 0.5f, + }, + judgementsContainer = new Container + { + Name = "judgements", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = judgement_line_width, + }, + new Circle + { + Name = "middle marker behind", + Colour = GetColourForHitResult(hitWindows.Last().result), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(centre_marker_size), + }, + new Circle + { + Name = "middle marker in front", + Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(centre_marker_size), + Scale = new Vector2(0.5f), + }, } }, - judgementsContainer = new Container + new Container { + Name = "average chevron", Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Width = judgement_line_width, + Width = chevron_size, RelativeSizeAxes = Axes.Y, + Child = arrow = new SpriteIcon + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Y = 0.5f, + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(chevron_size), + } }, } }; - createColourBars(); + createColourBars(hitWindows); } protected override void LoadComplete() @@ -149,10 +172,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters iconLate.Rotation = -Rotation; } - private void createColourBars() + private void createColourBars((HitResult result, double length)[] windows) { - var windows = HitWindows.GetAllAvailableWindows().ToArray(); - // max to avoid div-by-zero. maxHitWindow = Math.Max(1, windows.First().length); @@ -166,17 +187,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters colourBarsLate.Add(createColourBar(result, hitWindow, i == 0)); } - // a little nub to mark the centre point. - var centre = createColourBar(windows.Last().result, 0.01f); - centre.Anchor = centre.Origin = Anchor.CentreLeft; - centre.Width = 2.5f; - colourBars.Add(centre); - - Drawable createColourBar(HitResult result, float height, bool first = false) + Drawable createColourBar(HitResult result, float height, bool requireGradient = false) { var colour = GetColourForHitResult(result); - if (first) + if (requireGradient) { // the first bar needs gradient rendering. const float gradient_start = 0.8f; @@ -243,7 +258,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters judgementsContainer.Add(new JudgementLine { Y = getRelativeJudgementPosition(judgement.TimeOffset), - Origin = Anchor.CentreLeft, + Colour = GetColourForHitResult(judgement.Type), }); arrow.MoveToY( @@ -255,34 +270,40 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters internal class JudgementLine : CompositeDrawable { - private const int judgement_fade_duration = 5000; - public JudgementLine() { RelativeSizeAxes = Axes.X; RelativePositionAxes = Axes.Y; - Height = 3; + Height = judgement_line_height; - InternalChild = new CircularContainer + Blending = BlendingParameters.Additive; + + Origin = Anchor.Centre; + Anchor = Anchor.TopCentre; + + InternalChild = new Circle { - Masking = true, RelativeSizeAxes = Axes.Both, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - } }; } protected override void LoadComplete() { + const int judgement_fade_in_duration = 100; + const int judgement_fade_out_duration = 5000; + base.LoadComplete(); + Alpha = 0; Width = 0; - this.ResizeWidthTo(1, 200, Easing.OutElasticHalf); - this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration).Expire(); + this + .FadeTo(0.8f, judgement_fade_in_duration, Easing.OutQuint) + .ResizeWidthTo(1, judgement_fade_in_duration, Easing.OutQuint) + .Then() + .FadeOut(judgement_fade_out_duration, Easing.In) + .ResizeWidthTo(0, judgement_fade_out_duration, Easing.In) + .Expire(); } } From e91b3ae5f19fa70fa4062be6aef4e726f12ba10c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 19:18:47 +0900 Subject: [PATCH 116/339] Move constants closer to usages --- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 2f9206e0fb..521d8c01b1 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -18,19 +18,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public class BarHitErrorMeter : HitErrorMeter { - private const int arrow_move_duration = 400; - private const int judgement_line_width = 10; private const int judgement_line_height = 3; - private const int centre_marker_size = 6; - - private const int bar_height = 200; - - private const int bar_width = 2; - - private const float chevron_size = 8; - private SpriteIcon arrow; private SpriteIcon iconEarly; private SpriteIcon iconLate; @@ -50,6 +40,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [BackgroundDependencyLoader] private void load() { + const int centre_marker_size = 6; + const int bar_height = 200; + const int bar_width = 2; + const float chevron_size = 8; + const float icon_size = 14; + var hitWindows = HitWindows.GetAllAvailableWindows().ToArray(); InternalChild = new Container @@ -72,7 +68,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters iconEarly = new SpriteIcon { Y = -10, - Size = new Vector2(10), + Size = new Vector2(icon_size), Icon = FontAwesome.Solid.ShippingFast, Anchor = Anchor.TopCentre, Origin = Anchor.Centre, @@ -80,7 +76,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters iconLate = new SpriteIcon { Y = 10, - Size = new Vector2(10), + Size = new Vector2(icon_size), Icon = FontAwesome.Solid.Bicycle, Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, @@ -235,6 +231,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected override void OnNewJudgement(JudgementResult judgement) { + const int arrow_move_duration = 400; + if (!judgement.IsHit || judgement.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0) return; From 163cd48bf63ec43c3b9677340976544b109eca2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 19:27:53 +0900 Subject: [PATCH 117/339] Further metrics tweaking --- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 521d8c01b1..542731cf93 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -18,8 +18,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public class BarHitErrorMeter : HitErrorMeter { - private const int judgement_line_width = 10; - private const int judgement_line_height = 3; + private const int judgement_line_width = 14; + private const int judgement_line_height = 4; private SpriteIcon arrow; private SpriteIcon iconEarly; @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [BackgroundDependencyLoader] private void load() { - const int centre_marker_size = 6; + const int centre_marker_size = 8; const int bar_height = 200; const int bar_width = 2; const float chevron_size = 8; @@ -98,6 +98,14 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters RelativeSizeAxes = Axes.Y, Height = 0.5f, }, + new Circle + { + Name = "middle marker behind", + Colour = GetColourForHitResult(hitWindows.Last().result), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(centre_marker_size), + }, judgementsContainer = new Container { Name = "judgements", @@ -107,17 +115,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Width = judgement_line_width, }, new Circle - { - Name = "middle marker behind", - Colour = GetColourForHitResult(hitWindows.Last().result), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(centre_marker_size), - }, - new Circle { Name = "middle marker in front", - Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.5f), + Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f), Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(centre_marker_size), @@ -231,7 +231,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected override void OnNewJudgement(JudgementResult judgement) { - const int arrow_move_duration = 400; + const int arrow_move_duration = 800; if (!judgement.IsHit || judgement.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0) return; @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters arrow.MoveToY( getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1) - , arrow_move_duration, Easing.Out); + , arrow_move_duration, Easing.OutQuint); } private float getRelativeJudgementPosition(double value) => Math.Clamp((float)((value / maxHitWindow) + 1) / 2, 0, 1); @@ -296,11 +296,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Width = 0; this - .FadeTo(0.8f, judgement_fade_in_duration, Easing.OutQuint) + .FadeTo(0.6f, judgement_fade_in_duration, Easing.OutQuint) .ResizeWidthTo(1, judgement_fade_in_duration, Easing.OutQuint) .Then() - .FadeOut(judgement_fade_out_duration, Easing.In) - .ResizeWidthTo(0, judgement_fade_out_duration, Easing.In) + .FadeOut(judgement_fade_out_duration) + .ResizeWidthTo(0, judgement_fade_out_duration, Easing.InQuint) .Expire(); } } From 8fdf2b13ac2d83b206d88940e7582ae80c91a645 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 22:23:50 +0900 Subject: [PATCH 118/339] 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 839f7882bf..1b5461959a 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 c106c373c4..5e194e2aca 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 86cf1b229c..23101c5af6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 39c30516d0874b025d3e8e0610bd6ab096ebd2a6 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 18:31:13 +0000 Subject: [PATCH 119/339] Implement `ChannelControlItem` for new chat design Adds new component `ChannelControlItem` and it's child components to be used as the clickable control in the new chat sidebar for joined channels. Has public properties `HasUnread` and `MentionCount` to control the display of the channel having unread messages or mentions of the user. Channel select/join requests are exposed via `OnRequestSelect` and `OnRequestLeave` events respectively which should be handled by a parent component. Requires a cached `Bindable` instance to be managed by a parent component. Requires a cached `OveralayColourScheme` instance to be provided by a parent component. --- .../Online/TestSceneChannelControlItem.cs | 145 +++++++++++++++ .../Chat/ChannelControl/ControlItem.cs | 167 ++++++++++++++++++ .../Chat/ChannelControl/ControlItemAvatar.cs | 60 +++++++ .../Chat/ChannelControl/ControlItemClose.cs | 55 ++++++ .../Chat/ChannelControl/ControlItemMention.cs | 77 ++++++++ .../Chat/ChannelControl/ControlItemText.cs | 71 ++++++++ 6 files changed, 575 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs new file mode 100644 index 0000000000..a1431f696b --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.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 NUnit.Framework; +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.Shapes; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osu.Game.Overlays.Chat.ChannelControl; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneChannelControlItem : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + + [Cached] + private readonly Bindable selected = new Bindable(); + + private static List channels = new List + { + createPublicChannel("#public-channel"), + createPublicChannel("#public-channel-long-name"), + createPrivateChannel("test user", 2), + createPrivateChannel("test user long name", 3), + }; + + private readonly Dictionary channelMap = new Dictionary(); + + private FillFlowContainer flow; + private OsuSpriteText selectedText; + private OsuSpriteText leaveText; + + [SetUp] + public void SetUp() + { + Schedule(() => + { + Children = new Drawable[] + { + selectedText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -140, + }, + leaveText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -120, + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(190, 200), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6, + }, + flow = new FillFlowContainer + { + Spacing = new Vector2(20), + RelativeSizeAxes = Axes.Both, + }, + }, + }, + }; + + selected.BindValueChanged(change => + { + selectedText.Text = $"Selected Channel: {change.NewValue?.Name ?? "[null]"}"; + }, true); + + foreach (var channel in channels) + { + var item = new ControlItem(channel); + flow.Add(item); + channelMap.Add(channel, item); + item.OnRequestSelect += channel => selected.Value = channel; + item.OnRequestLeave += leaveChannel; + } + }); + } + + [Test] + public void TestVisual() + { + AddStep("Unread Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].HasUnread = true; + }); + + AddStep("Read Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].HasUnread = false; + }); + + AddStep("Add Mention Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].MentionCount++; + }); + + AddStep("Add 98 Mentions Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].MentionCount += 98; + }); + + AddStep("Clear Mentions Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].MentionCount = 0; + }); + } + + private void leaveChannel(Channel channel) + { + leaveText.Text = $"OnRequestLeave: {channel.Name}"; + leaveText.FadeIn().Then().FadeOut(1000, Easing.InQuint); + } + + private static Channel createPublicChannel(string name) => + new Channel { Name = name, Type = ChannelType.Public, Id = 1234 }; + + private static Channel createPrivateChannel(string username, int id) + => new Channel(new APIUser { Id = id, Username = username }); + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs new file mode 100644 index 0000000000..559f75f198 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -0,0 +1,167 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItem : OsuClickableContainer + { + public event Action? OnRequestSelect; + public event Action? OnRequestLeave; + + public int MentionCount + { + get => mention?.MentionCount ?? 0; + set + { + if (mention != null) + mention.MentionCount = value; + } + } + + public bool HasUnread + { + get => text?.HasUnread ?? false; + set + { + if (text != null) + text.HasUnread = value; + } + } + + private Box? hoverBox; + private Box? selectBox; + private ControlItemText? text; + private ControlItemMention? mention; + private ControlItemClose? close; + + [Resolved] + private Bindable? selectedChannel { get; set; } + + [Resolved] + private OverlayColourProvider? colourProvider { get; set; } + + private readonly Channel channel; + + public ControlItem(Channel channel) + { + this.channel = channel; + } + + [BackgroundDependencyLoader] + private void load() + { + Height = 30; + RelativeSizeAxes = Axes.X; + + Children = new Drawable[] + { + hoverBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider!.Background3, + Alpha = 0f, + }, + selectBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider!.Background4, + Alpha = 0f, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 18, Right = 5 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + createAvatar(), + text = new ControlItemText(channel) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + mention = new ControlItemMention + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 3 }, + }, + close = new ControlItemClose + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 3 }, + } + } + }, + }, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedChannel?.BindValueChanged(change => + { + if (change.NewValue == channel) + selectBox?.Show(); + else + selectBox?.Hide(); + }, true); + + Action = () => OnRequestSelect?.Invoke(channel); + close!.Action = () => OnRequestLeave?.Invoke(channel); + } + + protected override bool OnHover(HoverEvent e) + { + hoverBox?.Show(); + close?.Show(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoverBox?.Hide(); + close?.Hide(); + base.OnHoverLost(e); + } + + private Drawable createAvatar() + { + if (channel.Type != ChannelType.PM) + return Drawable.Empty(); + + return new ControlItemAvatar(channel) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }; + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs new file mode 100644 index 0000000000..9192f20cb1 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.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. + +#nullable enable + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Online.Chat; +using osu.Game.Users.Drawables; +using osuTK; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItemAvatar : CircularContainer + { + private DrawableAvatar? avatar; + private readonly Channel channel; + + public ControlItemAvatar(Channel channel) + { + this.channel = channel; + } + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(20); + Margin = new MarginPadding { Right = 5 }; + Masking = true; + + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.Solid.At, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Colour4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + }, + new DelayedLoadWrapper(avatar = new DrawableAvatar(channel.Users.First()) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + avatar!.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs new file mode 100644 index 0000000000..4b190e18fd --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.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.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItemClose : OsuClickableContainer + { + private readonly SpriteIcon icon; + + public ControlItemClose() + { + Alpha = 0f; + Size = new Vector2(20); + Child = icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.75f), + Icon = FontAwesome.Solid.TimesCircle, + RelativeSizeAxes = Axes.Both, + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + icon.ScaleTo(0.5f, 1000, Easing.OutQuint); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + icon.ScaleTo(0.75f, 1000, Easing.OutElastic); + base.OnMouseUp(e); + } + + protected override bool OnHover(HoverEvent e) + { + icon.FadeColour(Color4.Red, 200, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + icon.FadeColour(Color4.White, 200, Easing.OutQuint); + base.OnHoverLost(e); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs new file mode 100644 index 0000000000..12de154faa --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.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. + +#nullable enable + +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.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItemMention : CircularContainer + { + private int mentionCount = 0; + public int MentionCount + { + get => mentionCount; + set + { + if (value == mentionCount) + return; + + mentionCount = value; + updateText(); + } + } + + private OsuSpriteText? countText; + + [Resolved] + private OverlayColourProvider? colourProvider { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + Masking = true; + Size = new Vector2(20, 12); + Alpha = 0f; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider!.Colour1, + }, + countText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 11, weight: FontWeight.Bold), + Margin = new MarginPadding { Bottom = 1 }, + Colour = colourProvider.Background5, + }, + }; + + updateText(); + } + + private void updateText() + { + if (mentionCount > 99) + countText!.Text = "99+"; + else + countText!.Text = mentionCount.ToString(); + + if (mentionCount > 0) + this.Show(); + else + this.Hide(); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs new file mode 100644 index 0000000000..2b8f50e184 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.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. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItemText : Container + { + public bool HasUnread + { + get => hasUnread; + set + { + if (hasUnread == value) + return; + + hasUnread = value; + updateText(); + } + } + + private bool hasUnread = false; + private OsuSpriteText? text; + + [Resolved] + private OverlayColourProvider? colourProvider { get; set; } + + private readonly Channel channel; + + public ControlItemText(Channel channel) + { + this.channel = channel; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + Child = text = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = channel.Type == ChannelType.Public ? $"# {channel.Name.Substring(1)}" : channel.Name, + Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold), + Colour = colourProvider!.Light3, + Margin = new MarginPadding { Bottom = 2 }, + RelativeSizeAxes = Axes.X, + Truncate = true, + }; + } + + private void updateText() + { + if (!IsLoaded) + return; + + if (HasUnread) + text!.Colour = Colour4.White; + else + text!.Colour = colourProvider!.Light3; + } + } +} From c0d82dfb41478a97e633a60fe847930bf85bf7ca Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 19:42:55 +0000 Subject: [PATCH 120/339] Code quality fixes --- .../Visual/Online/TestSceneChannelControlItem.cs | 4 ++-- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 2 +- .../Chat/ChannelControl/ControlItemMention.cs | 12 +++++------- .../Overlays/Chat/ChannelControl/ControlItemText.cs | 3 ++- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index a1431f696b..64ad924ecb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly Bindable selected = new Bindable(); - private static List channels = new List + private static readonly List channels = new List { createPublicChannel("#public-channel"), createPublicChannel("#public-channel-long-name"), @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Online var item = new ControlItem(channel); flow.Add(item); channelMap.Add(channel, item); - item.OnRequestSelect += channel => selected.Value = channel; + item.OnRequestSelect += c => selected.Value = c; item.OnRequestLeave += leaveChannel; } }); diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index 559f75f198..e475ac7821 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl }, Content = new[] { - new Drawable[] + new[] { createAvatar(), text = new ControlItemText(channel) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 12de154faa..6af2e26af8 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -15,7 +15,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemMention : CircularContainer { - private int mentionCount = 0; + private int mentionCount; + public int MentionCount { get => mentionCount; @@ -63,15 +64,12 @@ namespace osu.Game.Overlays.Chat.ChannelControl private void updateText() { - if (mentionCount > 99) - countText!.Text = "99+"; - else - countText!.Text = mentionCount.ToString(); + countText!.Text = MentionCount > 99 ? "99+" : MentionCount.ToString(); if (mentionCount > 0) - this.Show(); + Show(); else - this.Hide(); + Hide(); } } } diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 2b8f50e184..bb88d733d4 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -14,6 +14,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemText : Container { + private bool hasUnread; + public bool HasUnread { get => hasUnread; @@ -27,7 +29,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl } } - private bool hasUnread = false; private OsuSpriteText? text; [Resolved] From e9f0ad33efca61ccbbb0a09c14f88615d3e2f819 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 20:06:31 +0000 Subject: [PATCH 121/339] Use autosizing for ChannelControlItem test scene --- .../Online/TestSceneChannelControlItem.cs | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 64ad924ecb..7f76dca013 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -45,36 +45,47 @@ namespace osu.Game.Tests.Visual.Online { Schedule(() => { - Children = new Drawable[] + Child = new FillFlowContainer { - selectedText = new OsuSpriteText + Direction = FillDirection.Vertical, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10), + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = -140, - }, - leaveText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = -120, - }, - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(190, 200), - Children = new Drawable[] + selectedText = new OsuSpriteText { - new Box + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + leaveText = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 16, + AlwaysPresent = true, + }, + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Y, + Width = 190, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background6, - }, - flow = new FillFlowContainer - { - Spacing = new Vector2(20), - RelativeSizeAxes = Axes.Both, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6, + }, + flow = new FillFlowContainer + { + Direction = FillDirection.Vertical, + Spacing = new Vector2(20), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, }, }, }, From 12472593cc4f2c7b36a8682c7d63d17315e7a9cf Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 20:14:04 +0000 Subject: [PATCH 122/339] Mark required dependencies as non-nullable --- .../Overlays/Chat/ChannelControl/ControlItem.cs | 14 +++++++------- .../Chat/ChannelControl/ControlItemAvatar.cs | 3 ++- .../Chat/ChannelControl/ControlItemMention.cs | 4 ++-- .../Chat/ChannelControl/ControlItemText.cs | 8 ++++---- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index e475ac7821..073173f2ff 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -40,6 +40,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl } } + private readonly Channel channel; + private Box? hoverBox; private Box? selectBox; private ControlItemText? text; @@ -47,12 +49,10 @@ namespace osu.Game.Overlays.Chat.ChannelControl private ControlItemClose? close; [Resolved] - private Bindable? selectedChannel { get; set; } + private Bindable selectedChannel { get; set; } = null!; [Resolved] - private OverlayColourProvider? colourProvider { get; set; } - - private readonly Channel channel; + private OverlayColourProvider colourProvider { get; set; } = null!; public ControlItem(Channel channel) { @@ -70,13 +70,13 @@ namespace osu.Game.Overlays.Chat.ChannelControl hoverBox = new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider!.Background3, + Colour = colourProvider.Background3, Alpha = 0f, }, selectBox = new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider!.Background4, + Colour = colourProvider.Background4, Alpha = 0f, }, new Container @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { base.LoadComplete(); - selectedChannel?.BindValueChanged(change => + selectedChannel.BindValueChanged(change => { if (change.NewValue == channel) selectBox?.Show(); diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs index 9192f20cb1..62b893f3bc 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs @@ -16,9 +16,10 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemAvatar : CircularContainer { - private DrawableAvatar? avatar; private readonly Channel channel; + private DrawableAvatar? avatar; + public ControlItemAvatar(Channel channel) { this.channel = channel; diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 6af2e26af8..693fb68217 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl private OsuSpriteText? countText; [Resolved] - private OverlayColourProvider? colourProvider { get; set; } + private OverlayColourProvider colourProvider { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider!.Colour1, + Colour = colourProvider.Colour1, }, countText = new OsuSpriteText { diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index bb88d733d4..9b0f5011fc 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -29,12 +29,12 @@ namespace osu.Game.Overlays.Chat.ChannelControl } } + private readonly Channel channel; + private OsuSpriteText? text; [Resolved] - private OverlayColourProvider? colourProvider { get; set; } - - private readonly Channel channel; + private OverlayColourProvider colourProvider { get; set; } = null!; public ControlItemText(Channel channel) { @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl Origin = Anchor.CentreLeft, Text = channel.Type == ChannelType.Public ? $"# {channel.Name.Substring(1)}" : channel.Name, Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold), - Colour = colourProvider!.Light3, + Colour = colourProvider.Light3, Margin = new MarginPadding { Bottom = 2 }, RelativeSizeAxes = Axes.X, Truncate = true, From e91af664ef080c250fbfd6997dd5aa1033f502d1 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 20:37:54 +0000 Subject: [PATCH 123/339] Adjust ControlItemAvatar placeholder animation and colour --- .../Overlays/Chat/ChannelControl/ControlItemAvatar.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs index 62b893f3bc..bb3109ec1f 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs @@ -18,6 +18,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { private readonly Channel channel; + private SpriteIcon? placeholder; private DrawableAvatar? avatar; public ControlItemAvatar(Channel channel) @@ -34,14 +35,14 @@ namespace osu.Game.Overlays.Chat.ChannelControl Children = new Drawable[] { - new SpriteIcon + placeholder = new SpriteIcon { Icon = FontAwesome.Solid.At, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = Colour4.Black, + Colour = Colour4.White, RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, + Alpha = 0.5f, }, new DelayedLoadWrapper(avatar = new DrawableAvatar(channel.Users.First()) { @@ -55,7 +56,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl protected override void LoadComplete() { base.LoadComplete(); - avatar!.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); + avatar!.OnLoadComplete += _ => placeholder!.FadeOut(250); } } } From 9621ef9437ca389fde4295e507d8681ee14ee3bb Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:10:39 +0000 Subject: [PATCH 124/339] Use OsuColour.Red1 for ControlItemClose hover colour --- .../Visual/Online/TestSceneChannelControlItem.cs | 4 ++++ .../Overlays/Chat/ChannelControl/ControlItemClose.cs | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 7f76dca013..9464d244be 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; @@ -23,6 +24,9 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + [Cached] + private readonly OsuColour osuColour = new OsuColour(); + [Cached] private readonly Bindable selected = new Bindable(); diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs index 4b190e18fd..4730d7b72d 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs @@ -1,12 +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.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.Chat.ChannelControl { @@ -14,6 +15,9 @@ namespace osu.Game.Overlays.Chat.ChannelControl { private readonly SpriteIcon icon; + [Resolved] + private OsuColour osuColour { get; set; } = null!; + public ControlItemClose() { Alpha = 0f; @@ -42,13 +46,13 @@ namespace osu.Game.Overlays.Chat.ChannelControl protected override bool OnHover(HoverEvent e) { - icon.FadeColour(Color4.Red, 200, Easing.OutQuint); + icon.FadeColour(osuColour.Red1, 200, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - icon.FadeColour(Color4.White, 200, Easing.OutQuint); + icon.FadeColour(Colour4.White, 200, Easing.OutQuint); base.OnHoverLost(e); } } From 3aa343b9870dc3487013151889880f282b29cafe Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:14:33 +0000 Subject: [PATCH 125/339] Use OsuColour.YellowLight for ControlItemMention background --- osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 693fb68217..370c435266 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -32,6 +32,9 @@ namespace osu.Game.Overlays.Chat.ChannelControl private OsuSpriteText? countText; + [Resolved] + private OsuColour osuColour { get; set; } = null!; + [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -47,7 +50,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Colour1, + Colour = osuColour.YellowLight, }, countText = new OsuSpriteText { From 1f0f6990f096d15a064eda731bd2dbad876ff786 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:16:28 +0000 Subject: [PATCH 126/339] Use ColourProvider.Content1 for ControlItemText colour --- osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 9b0f5011fc..0c91903f6b 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -64,9 +64,9 @@ namespace osu.Game.Overlays.Chat.ChannelControl return; if (HasUnread) - text!.Colour = Colour4.White; + text!.Colour = colourProvider.Content1; else - text!.Colour = colourProvider!.Light3; + text!.Colour = colourProvider.Light3; } } } From b01a809d551efcb342e59046ae087d46309323fe Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:26:33 +0000 Subject: [PATCH 127/339] Refactor ControlItemMention to use bindable flow --- .../Online/TestSceneChannelControlItem.cs | 6 +-- .../Chat/ChannelControl/ControlItem.cs | 11 +---- .../Chat/ChannelControl/ControlItemMention.cs | 41 ++++++++----------- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 9464d244be..2e5c2cc2cb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -129,19 +129,19 @@ namespace osu.Game.Tests.Visual.Online AddStep("Add Mention Selected", () => { if (selected.Value != null) - channelMap[selected.Value].MentionCount++; + channelMap[selected.Value].Mentions.Value++; }); AddStep("Add 98 Mentions Selected", () => { if (selected.Value != null) - channelMap[selected.Value].MentionCount += 98; + channelMap[selected.Value].Mentions.Value += 98; }); AddStep("Clear Mentions Selected", () => { if (selected.Value != null) - channelMap[selected.Value].MentionCount = 0; + channelMap[selected.Value].Mentions.Value = 0; }); } diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index 073173f2ff..d88a99c484 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -20,15 +20,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl public event Action? OnRequestSelect; public event Action? OnRequestLeave; - public int MentionCount - { - get => mention?.MentionCount ?? 0; - set - { - if (mention != null) - mention.MentionCount = value; - } - } + [Cached] + public readonly BindableInt Mentions = new BindableInt(); public bool HasUnread { diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 370c435266..594a52b8a7 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -4,6 +4,7 @@ #nullable enable using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -15,23 +16,11 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemMention : CircularContainer { - private int mentionCount; - - public int MentionCount - { - get => mentionCount; - set - { - if (value == mentionCount) - return; - - mentionCount = value; - updateText(); - } - } - private OsuSpriteText? countText; + [Resolved] + private BindableInt mentions { get; set; } = null!; + [Resolved] private OsuColour osuColour { get; set; } = null!; @@ -61,18 +50,24 @@ namespace osu.Game.Overlays.Chat.ChannelControl Colour = colourProvider.Background5, }, }; - - updateText(); } - private void updateText() + protected override void LoadComplete() { - countText!.Text = MentionCount > 99 ? "99+" : MentionCount.ToString(); + base.LoadComplete(); - if (mentionCount > 0) - Show(); - else - Hide(); + mentions.BindValueChanged(change => + { + int mentionCount = change.NewValue; + + countText!.Text = mentionCount > 99 ? "99+" : mentionCount.ToString(); + + if (mentionCount > 0) + Show(); + else + Hide(); + }, true); } + } } From 75958bf2702778784caee4c70ed3040170f16a5e Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:32:30 +0000 Subject: [PATCH 128/339] Refactor ControlItemText to use bindable flow for unread state --- .../Online/TestSceneChannelControlItem.cs | 4 +-- .../Chat/ChannelControl/ControlItem.cs | 11 ++---- .../Chat/ChannelControl/ControlItemText.cs | 35 +++++++------------ 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 2e5c2cc2cb..9af3b7613b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -117,13 +117,13 @@ namespace osu.Game.Tests.Visual.Online AddStep("Unread Selected", () => { if (selected.Value != null) - channelMap[selected.Value].HasUnread = true; + channelMap[selected.Value].Unread.Value = true; }); AddStep("Read Selected", () => { if (selected.Value != null) - channelMap[selected.Value].HasUnread = false; + channelMap[selected.Value].Unread.Value = false; }); AddStep("Add Mention Selected", () => diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index d88a99c484..4b1bbaec82 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -23,15 +23,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl [Cached] public readonly BindableInt Mentions = new BindableInt(); - public bool HasUnread - { - get => text?.HasUnread ?? false; - set - { - if (text != null) - text.HasUnread = value; - } - } + [Cached] + public readonly BindableBool Unread = new BindableBool(); private readonly Channel channel; diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 0c91903f6b..3573c72846 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -4,6 +4,7 @@ #nullable enable using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -14,25 +15,13 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemText : Container { - private bool hasUnread; - - public bool HasUnread - { - get => hasUnread; - set - { - if (hasUnread == value) - return; - - hasUnread = value; - updateText(); - } - } - private readonly Channel channel; private OsuSpriteText? text; + [Resolved] + private BindableBool unread { get; set; } = null!; + [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -58,15 +47,17 @@ namespace osu.Game.Overlays.Chat.ChannelControl }; } - private void updateText() + protected override void LoadComplete() { - if (!IsLoaded) - return; + base.LoadComplete(); - if (HasUnread) - text!.Colour = colourProvider.Content1; - else - text!.Colour = colourProvider.Light3; + unread.BindValueChanged(change => + { + if (change.NewValue) + text!.Colour = colourProvider.Content1; + else + text!.Colour = colourProvider.Light3; + }, true); } } } From ec61b88ec27796836ae380c5e845101e1f06f589 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:39:57 +0000 Subject: [PATCH 129/339] Adjust ControlItem padding --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index 4b1bbaec82..e944bf4c28 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 18, Right = 5 }, + Padding = new MarginPadding { Left = 18, Right = 10 }, Child = new GridContainer { RelativeSizeAxes = Axes.Both, From 73a0373b4ed0d2de982c84b236dafae6c02ee677 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:56:56 +0000 Subject: [PATCH 130/339] Code quality fixes --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 6 ++---- osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs | 1 - osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs | 5 +---- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index e944bf4c28..f2bab64371 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -30,8 +30,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl private Box? hoverBox; private Box? selectBox; - private ControlItemText? text; - private ControlItemMention? mention; private ControlItemClose? close; [Resolved] @@ -84,12 +82,12 @@ namespace osu.Game.Overlays.Chat.ChannelControl new[] { createAvatar(), - text = new ControlItemText(channel) + new ControlItemText(channel) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - mention = new ControlItemMention + new ControlItemMention { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 594a52b8a7..e9172d99ba 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -68,6 +68,5 @@ namespace osu.Game.Overlays.Chat.ChannelControl Hide(); }, true); } - } } diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 3573c72846..89845dfef8 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -53,10 +53,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl unread.BindValueChanged(change => { - if (change.NewValue) - text!.Colour = colourProvider.Content1; - else - text!.Colour = colourProvider.Light3; + text!.Colour = change.NewValue ? colourProvider.Content1 : colourProvider.Light3; }, true); } } From 6628b7c654789619617475f7114c84b35acde86a Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 22:21:18 +0000 Subject: [PATCH 131/339] Ensure existing items are expired and cleared in ChannelControlItem test setup --- osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 9af3b7613b..e496ef60d7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -49,6 +49,11 @@ namespace osu.Game.Tests.Visual.Online { Schedule(() => { + foreach (var item in channelMap.Values) + item.Expire(); + + channelMap.Clear(); + Child = new FillFlowContainer { Direction = FillDirection.Vertical, From 59d57a44d4c1b7901aab454671ccc9d611379bca Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 09:11:57 +0900 Subject: [PATCH 132/339] Prevent incorrect usages by hard-typing ctor type --- osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs | 2 +- osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs | 2 +- osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 2 +- osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs | 3 ++- .../Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs | 3 ++- .../TestSceneMultiplayerGameplayLeaderboardTeams.cs | 3 ++- 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 2c183f5d94..3de86c3fb1 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { - public CatchScoreProcessor(Ruleset ruleset) + public CatchScoreProcessor(CatchRuleset ruleset) : base(ruleset) { } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 0ffee809fc..7251c58eba 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Scoring { internal class ManiaScoreProcessor : ScoreProcessor { - public ManiaScoreProcessor(Ruleset ruleset) + public ManiaScoreProcessor(ManiaRuleset ruleset) : base(ruleset) { } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index e397fea0ed..8acb1cb59b 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { - public OsuScoreProcessor(Ruleset ruleset) + public OsuScoreProcessor(OsuRuleset ruleset) : base(ruleset) { } diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index ddf6587022..fb33ad25d9 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring { internal class TaikoScoreProcessor : ScoreProcessor { - public TaikoScoreProcessor(Ruleset ruleset) + public TaikoScoreProcessor(TaikoRuleset ruleset) : base(ruleset) { } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 0cc1257a07..1c0af829be 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -8,6 +8,7 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; @@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); - var scoreProcessor = new OsuScoreProcessor(Ruleset.Value.CreateInstance()); + var scoreProcessor = new OsuScoreProcessor(new OsuRuleset()); scoreProcessor.ApplyBeatmap(playable); LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 0edc56f5d3..6663180802 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -16,6 +16,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -64,7 +65,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Children = new Drawable[] { - scoreProcessor = new OsuScoreProcessor(Ruleset.Value.CreateInstance()), + scoreProcessor = new OsuScoreProcessor(new OsuRuleset()), }; scoreProcessor.ApplyBeatmap(playableBeatmap); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index 60a5785b31..62130ca5be 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -12,6 +12,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play.HUD; @@ -75,7 +76,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Children = new Drawable[] { - scoreProcessor = new OsuScoreProcessor(Ruleset.Value.CreateInstance()), + scoreProcessor = new OsuScoreProcessor(new OsuRuleset()), }; scoreProcessor.ApplyBeatmap(playableBeatmap); From 3a6d254d1f87a80a081346353ce8fe71d3a58ee3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 09:15:52 +0900 Subject: [PATCH 133/339] Add safeguards around incorrect ruleset sources --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b982c6ece2..0c585fac98 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -258,6 +258,9 @@ namespace osu.Game.Rulesets.Scoring /// The total score in the given . public double ComputeFinalScore(ScoringMode mode, ScoreInfo scoreInfo) { + if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) + throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); + extractFromStatistics(ruleset, scoreInfo.Statistics, out double extractedBaseScore, @@ -282,6 +285,9 @@ namespace osu.Game.Rulesets.Scoring /// The total score in the given . public double ComputePartialScore(ScoringMode mode, ScoreInfo scoreInfo) { + if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) + throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); + if (!beatmapApplied) throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}."); @@ -311,6 +317,9 @@ namespace osu.Game.Rulesets.Scoring /// The total score in the given . public double ComputeFinalLegacyScore(ScoringMode mode, ScoreInfo scoreInfo, int maxAchievableCombo) { + if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) + throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); + double accuracyRatio = scoreInfo.Accuracy; double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1; From ca6256049558e15a9266b0553afd91573ab42235 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 09:16:39 +0900 Subject: [PATCH 134/339] Resolve inspections --- osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index a9dcf705a9..2781f1958f 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -184,8 +184,8 @@ namespace osu.Game.Skinning.Editor public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException(); - public override string Description { get; } - public override string ShortName { get; } + public override string Description => string.Empty; + public override string ShortName => string.Empty; } } } From faf145742fc67bd22be37f9a3604c97a58a226bc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 09:21:09 +0900 Subject: [PATCH 135/339] Fix incorrect provided ruleset in test --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 5c01950bea..7ecd509193 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -28,7 +27,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [SetUp] public void SetUp() { - scoreProcessor = new ScoreProcessor(new OsuRuleset()); + scoreProcessor = new ScoreProcessor(new TestRuleset()); beatmap = new TestBeatmap(new RulesetInfo()) { HitObjects = new List From 4ff6879b85baed3b42560431b7c4962b7804c0d0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 11:30:57 +0900 Subject: [PATCH 136/339] Fix incorrect copied room end dates --- osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index fb8647284f..a2d3b7f4fc 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -334,6 +334,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge // ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not. r.RoomID.Value = null; + // Null out dates because end date is not supported client-side and the settings overlay will populate a duration. + r.EndDate.Value = null; + r.Duration.Value = null; + Open(r); joiningRoomOperation?.Dispose(); From daac93349864ee975aab450fd6b7d4b48e77bac6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 12:34:58 +0900 Subject: [PATCH 137/339] Remove unnecessary ctor arguments --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs | 4 ++-- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs | 4 ++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 4 ++-- .../Skinning/TestSceneDrawableTaikoMascot.cs | 2 +- osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs | 4 ++-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs | 3 +-- .../Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs | 3 +-- .../TestSceneMultiplayerGameplayLeaderboardTeams.cs | 3 +-- 12 files changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 45198fa0ca..3a7efb6263 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this); diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 3de86c3fb1..51b1ccaaba 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -7,8 +7,8 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { - public CatchScoreProcessor(CatchRuleset ruleset) - : base(ruleset) + public CatchScoreProcessor() + : base(new CatchRuleset()) { } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 92c77f405b..d04ef69dd2 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new ManiaHealthProcessor(drainStartTime, 0.5); diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 7251c58eba..02d62a090b 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -7,8 +7,8 @@ namespace osu.Game.Rulesets.Mania.Scoring { internal class ManiaScoreProcessor : ScoreProcessor { - public ManiaScoreProcessor(ManiaRuleset ruleset) - : base(ruleset) + public ManiaScoreProcessor() + : base(new ManiaRuleset()) { } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 87ffe8983f..92b90339f9 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 8acb1cb59b..ab0c0850dc 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { - public OsuScoreProcessor(OsuRuleset ruleset) - : base(ruleset) + public OsuScoreProcessor() + : base(new OsuRuleset()) { } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index b7e1c9586a..920a7cd1a1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [SetUp] public void SetUp() { - scoreProcessor = new TaikoScoreProcessor(new TaikoRuleset()); + scoreProcessor = new TaikoScoreProcessor(); } [Test] diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index fb33ad25d9..bacc22714e 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -7,8 +7,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring { internal class TaikoScoreProcessor : ScoreProcessor { - public TaikoScoreProcessor(TaikoRuleset ruleset) - : base(ruleset) + public TaikoScoreProcessor() + : base(new TaikoRuleset()) { } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index a3a2858bd5..8934b64ca5 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(); public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new TaikoHealthProcessor(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 1c0af829be..f57a54d84c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -8,7 +8,6 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; @@ -44,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); - var scoreProcessor = new OsuScoreProcessor(new OsuRuleset()); + var scoreProcessor = new OsuScoreProcessor(); scoreProcessor.ApplyBeatmap(playable); LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 6663180802..bcd4474876 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -16,7 +16,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -65,7 +64,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Children = new Drawable[] { - scoreProcessor = new OsuScoreProcessor(new OsuRuleset()), + scoreProcessor = new OsuScoreProcessor(), }; scoreProcessor.ApplyBeatmap(playableBeatmap); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index 62130ca5be..7f5aced925 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -12,7 +12,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Rooms; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play.HUD; @@ -76,7 +75,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Children = new Drawable[] { - scoreProcessor = new OsuScoreProcessor(new OsuRuleset()), + scoreProcessor = new OsuScoreProcessor(), }; scoreProcessor.ApplyBeatmap(playableBeatmap); From 523f668c8cae5211cab71fcc711d34aac70fdd94 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 12:37:39 +0900 Subject: [PATCH 138/339] Remove unnecessary ctor argument --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../Difficulty/CatchPerformanceCalculator.cs | 4 ++-- .../Difficulty/ManiaPerformanceCalculator.cs | 4 ++-- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Difficulty/TaikoPerformanceCalculator.cs | 4 ++-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 3a7efb6263..80b9436b2c 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Catch public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin); - public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator(this); + public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator(); public int LegacyID => 2; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 6fefb2e5bb..b30b85be2d 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty private int tinyTicksMissed; private int misses; - public CatchPerformanceCalculator(Ruleset ruleset) - : base(ruleset) + public CatchPerformanceCalculator() + : base(new CatchRuleset()) { } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 1eaf45e54a..b347cc9ae2 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty private int countMeh; private int countMiss; - public ManiaPerformanceCalculator(Ruleset ruleset) - : base(ruleset) + public ManiaPerformanceCalculator() + : base(new ManiaRuleset()) { } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index d04ef69dd2..bd6a67bf67 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this); - public override PerformanceCalculator CreatePerformanceCalculator() => new ManiaPerformanceCalculator(this); + public override PerformanceCalculator CreatePerformanceCalculator() => new ManiaPerformanceCalculator(); public const string SHORT_NAME = "mania"; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 5644b2009d..a93a1641a1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -22,8 +22,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double effectiveMissCount; - public OsuPerformanceCalculator(Ruleset ruleset) - : base(ruleset) + public OsuPerformanceCalculator() + : base(new OsuRuleset()) { } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 92b90339f9..2fdf42fca1 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Osu public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(RulesetInfo, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator() => new OsuPerformanceCalculator(this); + public override PerformanceCalculator CreatePerformanceCalculator() => new OsuPerformanceCalculator(); public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 9e73390fad..a8122551ff 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countMeh; private int countMiss; - public TaikoPerformanceCalculator(Ruleset ruleset) - : base(ruleset) + public TaikoPerformanceCalculator() + : base(new TaikoRuleset()) { } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 8934b64ca5..615fbf093f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Taiko public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator() => new TaikoPerformanceCalculator(this); + public override PerformanceCalculator CreatePerformanceCalculator() => new TaikoPerformanceCalculator(); public int LegacyID => 1; From 6d5692fcecc3e4c88b0bd7395b7b5a6258821885 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 14:25:05 +0900 Subject: [PATCH 139/339] Fix typo in setting name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Skinning/Components/BigBlackBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Components/BigBlackBox.cs b/osu.Game/Skinning/Components/BigBlackBox.cs index 8e57143a12..373e6467e8 100644 --- a/osu.Game/Skinning/Components/BigBlackBox.cs +++ b/osu.Game/Skinning/Components/BigBlackBox.cs @@ -23,7 +23,7 @@ namespace osu.Game.Skinning.Components { public bool UsesFixedAnchor { get; set; } - [SettingSource("Spining text", "Whether the big text should spin")] + [SettingSource("Spinning text", "Whether the big text should spin")] public Bindable TextSpin { get; } = new BindableBool(); [SettingSource("Alpha", "The alpha value of this box")] From 1814a325d8aef692bb174adf9266710f87b445ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 14:53:50 +0900 Subject: [PATCH 140/339] Move `GetSettingUnderlyingValue` to a `SettingSource` extension method --- .../Configuration/SettingSourceAttribute.cs | 34 ++++++++++++++++ osu.Game/Online/API/APIMod.cs | 9 ++--- .../API/ModSettingsDictionaryFormatter.cs | 4 +- osu.Game/Rulesets/Mods/Mod.cs | 8 ++-- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 3 +- osu.Game/Utils/ModUtils.cs | 39 +------------------ 6 files changed, 47 insertions(+), 50 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 5db502804d..4111a67b24 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using JetBrains.Annotations; @@ -170,6 +171,39 @@ namespace osu.Game.Configuration private static readonly ConcurrentDictionary property_info_cache = new ConcurrentDictionary(); + /// + /// Returns the underlying value of the given mod setting object. + /// Can be used for serialization and equality comparison purposes. + /// + /// A bindable. + public static object GetUnderlyingSettingValue(this object setting) + { + switch (setting) + { + case Bindable d: + return d.Value; + + case Bindable i: + return i.Value; + + case Bindable f: + return f.Value; + + case Bindable b: + return b.Value; + + case IBindable u: + // An unknown (e.g. enum) generic type. + var valueMethod = u.GetType().GetProperty(nameof(IBindable.Value)); + Debug.Assert(valueMethod != null); + return valueMethod.GetValue(u); + + default: + // fall back for non-bindable cases. + return setting; + } + } + public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj) { var type = obj.GetType(); diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 44b1c460d5..524f7b7108 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -12,7 +12,6 @@ using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Utils; namespace osu.Game.Online.API { @@ -43,7 +42,7 @@ namespace osu.Game.Online.API var bindable = (IBindable)property.GetValue(mod); if (!bindable.IsDefault) - Settings.Add(property.Name.Underscore(), ModUtils.GetSettingUnderlyingValue(bindable)); + Settings.Add(property.Name.Underscore(), bindable.GetUnderlyingSettingValue()); } } @@ -93,13 +92,13 @@ namespace osu.Game.Online.API public bool Equals(KeyValuePair x, KeyValuePair y) { - object xValue = ModUtils.GetSettingUnderlyingValue(x.Value); - object yValue = ModUtils.GetSettingUnderlyingValue(y.Value); + object xValue = x.Value.GetUnderlyingSettingValue(); + object yValue = y.Value.GetUnderlyingSettingValue(); return x.Key == y.Key && EqualityComparer.Default.Equals(xValue, yValue); } - public int GetHashCode(KeyValuePair obj) => HashCode.Combine(obj.Key, ModUtils.GetSettingUnderlyingValue(obj.Value)); + public int GetHashCode(KeyValuePair obj) => HashCode.Combine(obj.Key, obj.Value.GetUnderlyingSettingValue()); } } } diff --git a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs index 81ecc74ddb..a7c63c17f9 100644 --- a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs +++ b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Text; using MessagePack; using MessagePack.Formatters; -using osu.Game.Utils; +using osu.Game.Configuration; namespace osu.Game.Online.API { @@ -23,7 +23,7 @@ namespace osu.Game.Online.API var stringBytes = new ReadOnlySequence(Encoding.UTF8.GetBytes(kvp.Key)); writer.WriteString(in stringBytes); - primitiveFormatter.Serialize(ref writer, ModUtils.GetSettingUnderlyingValue(kvp.Value), options); + primitiveFormatter.Serialize(ref writer, kvp.Value.GetUnderlyingSettingValue(), options); } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 7136795461..b2d4be54ce 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Mods hashCode.Add(GetType()); foreach (var setting in Settings) - hashCode.Add(ModUtils.GetSettingUnderlyingValue(setting)); + hashCode.Add(setting.GetUnderlyingSettingValue()); return hashCode.ToHashCode(); } @@ -208,13 +208,13 @@ namespace osu.Game.Rulesets.Mods public bool Equals(IBindable x, IBindable y) { - object xValue = x == null ? null : ModUtils.GetSettingUnderlyingValue(x); - object yValue = y == null ? null : ModUtils.GetSettingUnderlyingValue(y); + object xValue = x?.GetUnderlyingSettingValue(); + object yValue = y?.GetUnderlyingSettingValue(); return EqualityComparer.Default.Equals(xValue, yValue); } - public int GetHashCode(IBindable obj) => ModUtils.GetSettingUnderlyingValue(obj).GetHashCode(); + public int GetHashCode(IBindable obj) => obj.GetUnderlyingSettingValue().GetHashCode(); } } } diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 5a51e7f455..95395f8181 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Skinning; -using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Play.HUD @@ -69,7 +68,7 @@ namespace osu.Game.Screens.Play.HUD var bindable = (IBindable)property.GetValue(component); if (!bindable.IsDefault) - Settings.Add(property.Name.Underscore(), ModUtils.GetSettingUnderlyingValue(bindable)); + Settings.Add(property.Name.Underscore(), bindable.GetUnderlyingSettingValue()); } if (component is Container container) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index cdca277dd8..d5ea74c404 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -1,18 +1,16 @@ // 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 System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using osu.Framework.Bindables; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -#nullable enable - namespace osu.Game.Utils { /// @@ -154,39 +152,6 @@ namespace osu.Game.Utils yield return mod; } - /// - /// Returns the underlying value of the given mod setting object. - /// Used in for serialization and equality comparison purposes. - /// - /// The mod setting. - public static object GetSettingUnderlyingValue(object setting) - { - switch (setting) - { - case Bindable d: - return d.Value; - - case Bindable i: - return i.Value; - - case Bindable f: - return f.Value; - - case Bindable b: - return b.Value; - - case IBindable u: - // A mod with unknown (e.g. enum) generic type. - var valueMethod = u.GetType().GetProperty(nameof(IBindable.Value)); - Debug.Assert(valueMethod != null); - return valueMethod.GetValue(u); - - default: - // fall back for non-bindable cases. - return setting; - } - } - /// /// Verifies all proposed mods are valid for a given ruleset and returns instantiated s for further processing. /// From 2b02a6555b2616dceadb941d0b5f01473547ef9b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 15:40:26 +0900 Subject: [PATCH 141/339] Remove current screen check from skin editor changes --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 73af2591c8..abd8272633 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -132,9 +132,6 @@ namespace osu.Game.Skinning.Editor { Debug.Assert(skinEditor != null); - if (!target.IsCurrentScreen()) - return; - if (!target.IsLoaded) { Scheduler.AddOnce(setTarget, target); From 9e476ced63eba9e4ad3e7e2f62d74faf67f079ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:35:46 +0900 Subject: [PATCH 142/339] Add `EditorSidebar` component --- .../UserInterface/TestSceneEditorSidebar.cs | 97 +++++++++++++++++++ .../Screens/Edit/Components/EditorSidebar.cs | 52 ++++++++++ .../Edit/Components/EditorSidebarSection.cs | 73 ++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs create mode 100644 osu.Game/Screens/Edit/Components/EditorSidebar.cs create mode 100644 osu.Game/Screens/Edit/Components/EditorSidebarSection.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs new file mode 100644 index 0000000000..7e2b5e0bad --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.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 System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; +using osu.Game.Screens.Edit.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneEditorSidebar : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + [Test] + public void TestSidebars() + { + AddStep("Add sidebars", () => + { + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + new EditorSidebar + { + Children = new[] + { + new EditorSidebarSection("Section 1") + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(3), + ChildrenEnumerable = Enumerable.Range(0, 10).Select(_ => new Box + { + Colour = Color4.White, + Size = new Vector2(32), + }) + }, + }, + new EditorSidebarSection("Section 2") + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(3), + ChildrenEnumerable = Enumerable.Range(0, 400).Select(_ => new Box + { + Colour = Color4.Gray, + Size = new Vector2(32), + }) + }, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + }, + new EditorSidebar + { + Children = new[] + { + new EditorSidebarSection("Section 1"), + new EditorSidebarSection("Section 2"), + }, + }, + } + } + } + }; + }); + } + } +} diff --git a/osu.Game/Screens/Edit/Components/EditorSidebar.cs b/osu.Game/Screens/Edit/Components/EditorSidebar.cs new file mode 100644 index 0000000000..cd7ef98401 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/EditorSidebar.cs @@ -0,0 +1,52 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Edit.Components +{ + /// + /// A sidebar area that can be attached to the left or right edge of the screen. + /// Houses scrolling sectionised content. + /// + internal class EditorSidebar : Container + { + private readonly Box background; + + protected override Container Content { get; } + + public EditorSidebar() + { + Width = 250; + RelativeSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + Padding = new MarginPadding { Left = 20 }, + ScrollbarOverlapsContent = false, + RelativeSizeAxes = Axes.Both, + Child = Content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + background.Colour = colourProvider.Background5; + } + } +} diff --git a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs new file mode 100644 index 0000000000..5c000471a6 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs @@ -0,0 +1,73 @@ +using osu.Framework.Allocation; +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.Overlays; +using osuTK; + +namespace osu.Game.Screens.Edit.Components +{ + public class EditorSidebarSection : Container + { + protected override Container Content { get; } + + public EditorSidebarSection(LocalisableString sectionName) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new SectionHeader(sectionName), + Content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + } + }; + } + + public class SectionHeader : CompositeDrawable + { + private readonly LocalisableString text; + + public SectionHeader(LocalisableString text) + { + this.text = text; + + Margin = new MarginPadding { Vertical = 10, Horizontal = 5 }; + + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Text = text, + Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), + }, + new Circle + { + Y = 18, + Colour = colourProvider.Highlight1, + Size = new Vector2(28, 2), + } + }; + } + } + } +} \ No newline at end of file From 4ab5d6e3f0b76ad04067fdfb274b188b2c29c0f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:46:57 +0900 Subject: [PATCH 143/339] Remove unnecessary `FillFlowContainer` from section --- osu.Game/Screens/Edit/Components/EditorSidebarSection.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs index 5c000471a6..319ad82b79 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs @@ -27,11 +27,10 @@ namespace osu.Game.Screens.Edit.Components Children = new Drawable[] { new SectionHeader(sectionName), - Content = new FillFlowContainer + Content = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, }, } }; @@ -70,4 +69,4 @@ namespace osu.Game.Screens.Edit.Components } } } -} \ No newline at end of file +} From a0a033520f17b765f91a9a09d346a0cbc115c2b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:48:14 +0900 Subject: [PATCH 144/339] Rider no add licence headers --- osu.Game/Screens/Edit/Components/EditorSidebar.cs | 3 +++ osu.Game/Screens/Edit/Components/EditorSidebarSection.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Screens/Edit/Components/EditorSidebar.cs b/osu.Game/Screens/Edit/Components/EditorSidebar.cs index cd7ef98401..4edcef41b1 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebar.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebar.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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs index 319ad82b79..d403694bde 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebarSection.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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; From c56df80106c50e4281aa8a28291c852b99cafecc Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 20:38:04 +0000 Subject: [PATCH 145/339] Fix `PlaylistResultsScreen` test failures As seen: https://github.com/ppy/osu/pull/17252/checks?check_run_id=5545717506 The `PlaylistsResultsScreen` takes a lease on the `Beatmap` bindable when entered. During `SetUp`, the `Beatmap` bindable is reassigned but fails in cases where an existing test instance of the screen hasn't been exited yet. This fix moves the assignment into the `SetUpSteps` function after `base.SetUpSteps` is called which will exit the existing screens first. --- .../TestScenePlaylistsResultsScreen.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 67894bab38..82cca1f61e 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -53,12 +53,23 @@ namespace osu.Game.Tests.Visual.Playlists userScore.Statistics = new Dictionary(); bindHandler(); - - // beatmap is required to be an actual beatmap so the scores can get their scores correctly calculated for standardised scoring. - // else the tests that rely on ordering will fall over. - Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); }); + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + // Existing test instances of the results screen hold a leased bindable of the beatmap, + // so wait for those screens to be cleaned up by the base SetUpSteps before re-assigning + AddStep("Create Working Beatmap", () => + { + // Beatmap is required to be an actual beatmap so the scores can get their scores correctly calculated for standardised scoring. + // else the tests that rely on ordering will fall over. + Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); + }); + } + [Test] public void TestShowWithUserScore() { From 1c79083f96dfb6bd9eb848caf02f180f8ff761da Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:15:35 +0000 Subject: [PATCH 146/339] Move all `PlaylistResultScreen` test state initialisation into `SetUpSteps` --- .../TestScenePlaylistsResultsScreen.cs | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 82cca1f61e..ee9a0e263b 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -40,32 +40,30 @@ namespace osu.Game.Tests.Visual.Playlists private int totalCount; private ScoreInfo userScore; - [SetUp] - public void Setup() => Schedule(() => - { - lowestScoreId = 1; - highestScoreId = 1; - requestComplete = false; - totalCount = 0; - - userScore = TestResources.CreateTestScoreInfo(); - userScore.TotalScore = 0; - userScore.Statistics = new Dictionary(); - - bindHandler(); - }); - [SetUpSteps] public override void SetUpSteps() { base.SetUpSteps(); - // Existing test instances of the results screen hold a leased bindable of the beatmap, - // so wait for those screens to be cleaned up by the base SetUpSteps before re-assigning - AddStep("Create Working Beatmap", () => + // Previous test instances of the results screen may still exist at this point so wait for + // those screens to be cleaned up by the base SetUpSteps before re-initialising test state. + // The the screen also holds a leased Beatmap bindable so reassigning it must happen after + // the screen as been exited. + AddStep("initialise user scores and beatmap", () => { - // Beatmap is required to be an actual beatmap so the scores can get their scores correctly calculated for standardised scoring. - // else the tests that rely on ordering will fall over. + lowestScoreId = 1; + highestScoreId = 1; + requestComplete = false; + totalCount = 0; + + userScore = TestResources.CreateTestScoreInfo(); + userScore.TotalScore = 0; + userScore.Statistics = new Dictionary(); + + bindHandler(); + + // Beatmap is required to be an actual beatmap so the scores can get their scores correctly + // calculated for standardised scoring, else the tests that rely on ordering will fall over. Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); }); } From 7621e779fa8667110e1b477c5fba3481627ea7f1 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:19:58 +0000 Subject: [PATCH 147/339] Move `ControlItem` Action assignments into BDL --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index f2bab64371..d886e8b45a 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -98,12 +98,15 @@ namespace osu.Game.Overlays.Chat.ChannelControl Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Margin = new MarginPadding { Right = 3 }, + Action = () => OnRequestLeave?.Invoke(channel), } } }, }, }, }; + + Action = () => OnRequestSelect?.Invoke(channel); } protected override void LoadComplete() @@ -117,9 +120,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl else selectBox?.Hide(); }, true); - - Action = () => OnRequestSelect?.Invoke(channel); - close!.Action = () => OnRequestLeave?.Invoke(channel); } protected override bool OnHover(HoverEvent e) From 481b8fe80bc043b6a70ad1b799fefd94ba58af06 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:22:32 +0000 Subject: [PATCH 148/339] Use `Orange1` for `ControlItemMention` background colour --- osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index e9172d99ba..5118386977 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl new Box { RelativeSizeAxes = Axes.Both, - Colour = osuColour.YellowLight, + Colour = osuColour.Orange1, }, countText = new OsuSpriteText { From 49b74d78670d85ef7a3b159b43b9504c5ff7156a Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:33:36 +0000 Subject: [PATCH 149/339] Use `BindTarget` instead of caching for `ControlItem` mentions bindable flow --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 2 +- .../Overlays/Chat/ChannelControl/ControlItemMention.cs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index d886e8b45a..d73ebed817 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -20,7 +20,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl public event Action? OnRequestSelect; public event Action? OnRequestLeave; - [Cached] public readonly BindableInt Mentions = new BindableInt(); [Cached] @@ -92,6 +91,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Margin = new MarginPadding { Right = 3 }, + Mentions = { BindTarget = Mentions }, }, close = new ControlItemClose { diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 5118386977..beb2713c92 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -16,10 +16,9 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemMention : CircularContainer { - private OsuSpriteText? countText; + public readonly BindableInt Mentions = new BindableInt(); - [Resolved] - private BindableInt mentions { get; set; } = null!; + private OsuSpriteText? countText; [Resolved] private OsuColour osuColour { get; set; } = null!; @@ -56,7 +55,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { base.LoadComplete(); - mentions.BindValueChanged(change => + Mentions.BindValueChanged(change => { int mentionCount = change.NewValue; From e38d9eafa053a4ddc0e374cbead1063a3f194d8c Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:37:15 +0000 Subject: [PATCH 150/339] Use `BindTarget` instead of caching for `ControlItem` unread flow --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 2 +- osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index d73ebed817..cc00550965 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -22,7 +22,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl public readonly BindableInt Mentions = new BindableInt(); - [Cached] public readonly BindableBool Unread = new BindableBool(); private readonly Channel channel; @@ -85,6 +84,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Unread = { BindTarget = Unread }, }, new ControlItemMention { diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 89845dfef8..490edb5d28 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -15,13 +15,12 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemText : Container { + public readonly BindableBool Unread = new BindableBool(); + private readonly Channel channel; private OsuSpriteText? text; - [Resolved] - private BindableBool unread { get; set; } = null!; - [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -51,7 +50,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { base.LoadComplete(); - unread.BindValueChanged(change => + Unread.BindValueChanged(change => { text!.Colour = change.NewValue ? colourProvider.Content1 : colourProvider.Light3; }, true); From ba1642a680d34ffc5c1f26953735965031542f22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 17:19:08 +0900 Subject: [PATCH 151/339] Allow section headers to wrap --- .../UserInterface/TestSceneEditorSidebar.cs | 2 +- .../Edit/Components/EditorSidebarSection.cs | 32 ++++++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs index 7e2b5e0bad..f2f475e063 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.UserInterface }) }, }, - new EditorSidebarSection("Section 2") + new EditorSidebarSection("Section with a really long section header") { Child = new FillFlowContainer { diff --git a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs index d403694bde..3871720562 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs @@ -7,7 +7,7 @@ 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.Containers; using osu.Game.Overlays; using osuTK; @@ -49,24 +49,32 @@ namespace osu.Game.Screens.Edit.Components Margin = new MarginPadding { Vertical = 10, Horizontal = 5 }; - AutoSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; } [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - InternalChildren = new Drawable[] + InternalChild = new FillFlowContainer { - new OsuSpriteText + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2), + Children = new Drawable[] { - Text = text, - Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), - }, - new Circle - { - Y = 18, - Colour = colourProvider.Highlight1, - Size = new Vector2(28, 2), + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold)) + { + Text = text, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + new Circle + { + Colour = colourProvider.Highlight1, + Size = new Vector2(28, 2), + } } }; } From 603527d72d09eaf0e727cf075e98fb9cdc78d51c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 18:38:01 +0900 Subject: [PATCH 152/339] Fix potential crash when highlighting chat messages Test failed locally in `TestPublicChannelMention`. This test seems to specify that the same message may arrive twice with the same ID, so rather than overthinking this one I propose we just use `FirstOrDefault`. ```csharp TearDown : System.AggregateException : One or more errors occurred. (Sequence contains more than one matching element) ----> System.InvalidOperationException : Sequence contains more than one matching element --TearDown at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) at osu.Framework.Extensions.TaskExtensions.WaitSafely(Task task) at osu.Framework.Testing.TestScene.checkForErrors() --InvalidOperationException at System.Linq.ThrowHelper.ThrowMoreThanOneMatchException() at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Func`2 predicate, Boolean& found) at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source, Func`2 predicate) at osu.Game.Overlays.Chat.DrawableChannel.b__14_0() in /Users/dean/Projects/osu/osu.Game/Overlays/Chat/DrawableChannel.cs:line 102 at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() ``` --- 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 632517aa31..161fe1d5be 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Chat if (highlightedMessage.Value == null) return; - var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(highlightedMessage.Value)); + var chatLine = chatLines.FirstOrDefault(c => c.Message.Equals(highlightedMessage.Value)); if (chatLine == null) return; From 2452d84e98fab4a975caef4af19fac619143047d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 18:44:30 +0900 Subject: [PATCH 153/339] Add missing `Schedule` call to allow individual tests from `TestSceneMessageNotifier` to pass --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 175d2ea36b..2c253650d5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Online private int messageIdCounter; [SetUp] - public void Setup() + public void Setup() => Schedule(() => { if (API is DummyAPIAccess daa) { @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Online testContainer.ChatOverlay.Show(); }); - } + }); private bool dummyAPIHandleRequest(APIRequest request) { From 99e3161cf0a40e6e6bfc70d201a967df29f2bcf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 18:00:32 +0900 Subject: [PATCH 154/339] Fix `SkinEditor`'s initial target not being a `Screen` --- osu.Game/Skinning/Editor/SkinEditor.cs | 14 +++++++++----- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 18 ++++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index ef26682c03..19c39d23d5 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -49,16 +48,20 @@ namespace osu.Game.Skinning.Editor private EditorToolboxGroup settingsToolbox; + public SkinEditor() + { + } + public SkinEditor(Drawable targetScreen) { - RelativeSizeAxes = Axes.Both; - UpdateTargetScreen(targetScreen); } [BackgroundDependencyLoader] private void load() { + RelativeSizeAxes = Axes.Both; + InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -155,7 +158,7 @@ namespace osu.Game.Skinning.Editor Scheduler.AddOnce(skinChanged); }, true); - SelectedComponents.BindCollectionChanged(selectionChanged); + SelectedComponents.BindCollectionChanged((_, __) => Scheduler.AddOnce(populateSettings), true); } public void UpdateTargetScreen(Drawable targetScreen) @@ -163,6 +166,7 @@ namespace osu.Game.Skinning.Editor this.targetScreen = targetScreen; SelectedComponents.Clear(); + Scheduler.AddOnce(loadBlueprintContainer); void loadBlueprintContainer() @@ -224,7 +228,7 @@ namespace osu.Game.Skinning.Editor SelectedComponents.Add(component); } - private void selectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void populateSettings() { settingsToolbox.Clear(); diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 08cdbf0aa9..1bab499979 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -21,16 +21,18 @@ namespace osu.Game.Skinning.Editor /// public class SkinEditorOverlay : CompositeDrawable, IKeyBindingHandler { - private readonly ScalingContainer target; + private readonly ScalingContainer scalingContainer; [CanBeNull] private SkinEditor skinEditor; public const float VISIBLE_TARGET_SCALE = 0.8f; - public SkinEditorOverlay(ScalingContainer target) + private Screen lastTargetScreen; + + public SkinEditorOverlay(ScalingContainer scalingContainer) { - this.target = target; + this.scalingContainer = scalingContainer; RelativeSizeAxes = Axes.Both; } @@ -77,7 +79,7 @@ namespace osu.Game.Skinning.Editor return; } - var editor = new SkinEditor(target); + var editor = new SkinEditor(); editor.State.BindValueChanged(editorVisibilityChanged); skinEditor = editor; @@ -95,6 +97,8 @@ namespace osu.Game.Skinning.Editor return; AddInternal(editor); + + SetTarget(lastTargetScreen); }); }); } @@ -105,11 +109,11 @@ namespace osu.Game.Skinning.Editor if (visibility.NewValue == Visibility.Visible) { - target.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); + scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); } else { - target.SetCustomRect(null); + scalingContainer.SetCustomRect(null); } } @@ -122,6 +126,8 @@ namespace osu.Game.Skinning.Editor /// public void SetTarget(Screen screen) { + lastTargetScreen = screen; + if (skinEditor == null) return; skinEditor.Save(); From 86960c791f783c614ada963bc105f065f42e3cfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 18:10:30 +0900 Subject: [PATCH 155/339] Close overlays and toolbar on entering the skin editor --- osu.Game/OsuGame.cs | 2 +- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ae117d03d2..25bd3d71de 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1184,7 +1184,7 @@ namespace osu.Game BackButton.Hide(); } - skinEditor.SetTarget((Screen)newScreen); + skinEditor.SetTarget((OsuScreen)newScreen); } private void screenPushed(IScreen lastScreen, IScreen newScreen) => screenChanged(lastScreen, newScreen); diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 1bab499979..2fdb16abfc 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -3,15 +3,16 @@ using System.Diagnostics; using JetBrains.Annotations; +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.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; +using osu.Game.Screens; namespace osu.Game.Skinning.Editor { @@ -28,7 +29,10 @@ namespace osu.Game.Skinning.Editor public const float VISIBLE_TARGET_SCALE = 0.8f; - private Screen lastTargetScreen; + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + + private OsuScreen lastTargetScreen; public SkinEditorOverlay(ScalingContainer scalingContainer) { @@ -105,15 +109,23 @@ namespace osu.Game.Skinning.Editor private void editorVisibilityChanged(ValueChangedEvent visibility) { + Debug.Assert(skinEditor != null); + const float toolbar_padding_requirement = 0.18f; if (visibility.NewValue == Visibility.Visible) { scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); + + game?.Toolbar.Hide(); + game?.CloseAllOverlays(); } else { scalingContainer.SetCustomRect(null); + + if (lastTargetScreen?.HideOverlaysOnEnter != true) + game?.Toolbar.Show(); } } @@ -124,7 +136,7 @@ namespace osu.Game.Skinning.Editor /// /// Set a new target screen which will be used to find skinnable components. /// - public void SetTarget(Screen screen) + public void SetTarget(OsuScreen screen) { lastTargetScreen = screen; @@ -136,7 +148,7 @@ namespace osu.Game.Skinning.Editor Scheduler.AddOnce(setTarget, screen); } - private void setTarget(Screen target) + private void setTarget(OsuScreen target) { Debug.Assert(skinEditor != null); From c807ad7e4ee131563ed45079bfde1e00d4226ae3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 19:11:17 +0900 Subject: [PATCH 156/339] Ensure toolbar is hidden even when the active screen is changed while the editor is open --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 2fdb16abfc..780e3d5b67 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -84,7 +83,7 @@ namespace osu.Game.Skinning.Editor } var editor = new SkinEditor(); - editor.State.BindValueChanged(editorVisibilityChanged); + editor.State.BindValueChanged(visibility => updateComponentVisibility()); skinEditor = editor; @@ -107,13 +106,13 @@ namespace osu.Game.Skinning.Editor }); } - private void editorVisibilityChanged(ValueChangedEvent visibility) + private void updateComponentVisibility() { Debug.Assert(skinEditor != null); const float toolbar_padding_requirement = 0.18f; - if (visibility.NewValue == Visibility.Visible) + if (skinEditor.State.Value == Visibility.Visible) { scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); @@ -144,6 +143,9 @@ namespace osu.Game.Skinning.Editor skinEditor.Save(); + // ensure the toolbar is re-hidden even if a new screen decides to try and show it. + updateComponentVisibility(); + // AddOnce with parameter will ensure the newest target is loaded if there is any overlap. Scheduler.AddOnce(setTarget, screen); } From d062810ff2b4c04019f2523faa58e2d3cecca758 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Mar 2022 23:30:46 +0900 Subject: [PATCH 157/339] Add basic scene selector --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 2 +- osu.Game/Skinning/Editor/SkinEditor.cs | 53 +++++++++++++++++++ osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 2 +- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index a0602e21b9..9012492028 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("reload skin editor", () => { skinEditor?.Expire(); - Player.ScaleTo(SkinEditorOverlay.VISIBLE_TARGET_SCALE); + Player.ScaleTo(0.8f); LoadComponentAsync(skinEditor = new SkinEditor(Player), Add); }); } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 19c39d23d5..37900d9920 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -10,14 +10,20 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components.Menus; +using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Screens.Play; +using osu.Game.Screens.Select; +using osuTK; namespace osu.Game.Skinning.Editor { @@ -42,6 +48,12 @@ namespace osu.Game.Skinning.Editor [Resolved] private OsuColour colours { get; set; } + [Resolved] + private IBindable ruleset { get; set; } + + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + private bool hasBegunMutating; private Container content; @@ -105,6 +117,47 @@ namespace osu.Game.Skinning.Editor }, }, }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + Y = 45, + Height = 30, + Name = "Scene library", + Spacing = new Vector2(10), + Padding = new MarginPadding(10), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new PurpleTriangleButton + { + Text = "Song Select", + Width = 100, + Height = 30, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is SongSelect) + return; + + screen.Push(new PlaySongSelect()); + }, new[] { typeof(SongSelect) }) + }, + new PurpleTriangleButton + { + Text = "Gameplay", + Width = 100, + Height = 30, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is Player) + return; + + var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); + if (replayGeneratingMod != null) + screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); + }, new[] { typeof(Player) }) + }, + } + }, new GridContainer { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 780e3d5b67..9fc233d3e3 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -114,7 +114,7 @@ namespace osu.Game.Skinning.Editor if (skinEditor.State.Value == Visibility.Visible) { - scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); + scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.2f, 0.8f - toolbar_padding_requirement, 0.7f), true); game?.Toolbar.Hide(); game?.CloseAllOverlays(); From 8d85723a62f1e0ad3f0d6b9e2e2480ce877b0b72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Mar 2022 19:23:57 +0900 Subject: [PATCH 158/339] Split out `SceneLibrary` into its own component --- osu.Game/Skinning/Editor/SkinEditor.cs | 126 ++++++++++++++++--------- 1 file changed, 82 insertions(+), 44 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 37900d9920..9d5d496837 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -6,8 +6,10 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Screens; @@ -24,6 +26,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osuTK; +using osuTK.Graphics; namespace osu.Game.Skinning.Editor { @@ -48,12 +51,6 @@ namespace osu.Game.Skinning.Editor [Resolved] private OsuColour colours { get; set; } - [Resolved] - private IBindable ruleset { get; set; } - - [Resolved(canBeNull: true)] - private OsuGame game { get; set; } - private bool hasBegunMutating; private Container content; @@ -117,47 +114,12 @@ namespace osu.Game.Skinning.Editor }, }, }, - new FillFlowContainer + new SceneLibrary { RelativeSizeAxes = Axes.X, Y = 45, - Height = 30, - Name = "Scene library", - Spacing = new Vector2(10), - Padding = new MarginPadding(10), - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new PurpleTriangleButton - { - Text = "Song Select", - Width = 100, - Height = 30, - Action = () => game?.PerformFromScreen(screen => - { - if (screen is SongSelect) - return; - - screen.Push(new PlaySongSelect()); - }, new[] { typeof(SongSelect) }) - }, - new PurpleTriangleButton - { - Text = "Gameplay", - Width = 100, - Height = 30, - Action = () => game?.PerformFromScreen(screen => - { - if (screen is Player) - return; - - var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); - if (replayGeneratingMod != null) - screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); - }, new[] { typeof(Player) }) - }, - } }, + new GridContainer { RelativeSizeAxes = Axes.Both, @@ -226,7 +188,10 @@ namespace osu.Game.Skinning.Editor { content.Children = new Drawable[] { - new SkinBlueprintContainer(targetScreen), + new SkinBlueprintContainer(targetScreen) + { + Margin = new MarginPadding { Top = 100 }, + } }; } } @@ -345,5 +310,78 @@ namespace osu.Game.Skinning.Editor foreach (var item in items) availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item); } + + private class SceneLibrary : CompositeDrawable + { + public const float HEIGHT = 30; + private const float padding = 10; + + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new OsuScrollContainer(Direction.Horizontal) + { + RelativeSizeAxes = Axes.X, + Height = HEIGHT + padding * 2, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f) + }, + new FillFlowContainer + { + Name = "Scene library", + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Spacing = new Vector2(padding), + Padding = new MarginPadding(padding), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new PurpleTriangleButton + { + Text = "Song Select", + Width = 100, + Height = HEIGHT, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is SongSelect) + return; + + screen.Push(new PlaySongSelect()); + }, new[] { typeof(SongSelect) }) + }, + new PurpleTriangleButton + { + Text = "Gameplay", + Width = 100, + Height = HEIGHT, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is Player) + return; + + var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); + if (replayGeneratingMod != null) + screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); + }, new[] { typeof(Player), typeof(SongSelect) }) + }, + } + }, + } + } + }; + } + } } } From c6aa32a003925fcde04c258409e7685016e9b95e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Mar 2022 23:26:27 +0900 Subject: [PATCH 159/339] Add basic song select setup for skinnability --- osu.Game/Screens/Select/SongSelect.cs | 5 +++++ osu.Game/Skinning/DefaultSkin.cs | 8 ++++++++ osu.Game/Skinning/SkinnableTarget.cs | 3 ++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f5b11448f8..2d1a2bce4e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -37,6 +37,7 @@ using osu.Game.Graphics.UserInterface; using System.Diagnostics; using osu.Game.Screens.Play; using osu.Game.Database; +using osu.Game.Skinning; namespace osu.Game.Screens.Select { @@ -235,6 +236,10 @@ namespace osu.Game.Screens.Select } } }, + new SkinnableTargetContainer(SkinnableTarget.SongSelect) + { + RelativeSizeAxes = Axes.Both, + }, }); if (ShowFooter) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 951e3f9cc5..7c6d138f4c 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -70,6 +70,14 @@ namespace osu.Game.Skinning case SkinnableTargetComponent target: switch (target.Target) { + case SkinnableTarget.SongSelect: + var songSelectComponents = new SkinnableTargetComponentsContainer(container => + { + // do stuff when we need to. + }); + + return songSelectComponents; + case SkinnableTarget.MainHUDComponents: var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container => { diff --git a/osu.Game/Skinning/SkinnableTarget.cs b/osu.Game/Skinning/SkinnableTarget.cs index 7b1eae126c..09de8a5d71 100644 --- a/osu.Game/Skinning/SkinnableTarget.cs +++ b/osu.Game/Skinning/SkinnableTarget.cs @@ -5,6 +5,7 @@ namespace osu.Game.Skinning { public enum SkinnableTarget { - MainHUDComponents + MainHUDComponents, + SongSelect } } From aff6a5a428d6ff232835d844066161b45b0f7f45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 15:11:47 +0900 Subject: [PATCH 160/339] Better align scene selector with menu bar --- osu.Game/Skinning/Editor/SkinEditor.cs | 27 +++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 9d5d496837..921849c0aa 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -26,7 +26,6 @@ using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osuTK; -using osuTK.Graphics; namespace osu.Game.Skinning.Editor { @@ -71,6 +70,8 @@ namespace osu.Game.Skinning.Editor { RelativeSizeAxes = Axes.Both; + const float menu_height = 40; + InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -78,10 +79,10 @@ namespace osu.Game.Skinning.Editor { new Container { - Name = "Top bar", + Name = "Menu container", RelativeSizeAxes = Axes.X, Depth = float.MinValue, - Height = 40, + Height = menu_height, Children = new Drawable[] { new EditorMenuBar @@ -117,7 +118,7 @@ namespace osu.Game.Skinning.Editor new SceneLibrary { RelativeSizeAxes = Axes.X, - Y = 45, + Y = menu_height, }, new GridContainer @@ -322,22 +323,26 @@ namespace osu.Game.Skinning.Editor [Resolved] private IBindable ruleset { get; set; } + public SceneLibrary() + { + Height = HEIGHT + padding * 2; + } + [BackgroundDependencyLoader] private void load() { InternalChildren = new Drawable[] { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("222") + }, new OsuScrollContainer(Direction.Horizontal) { - RelativeSizeAxes = Axes.X, - Height = HEIGHT + padding * 2, + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f) - }, new FillFlowContainer { Name = "Scene library", From ee3715f5cfe7e10f1f44d4a564ea0977aebc7bbf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 15:33:01 +0900 Subject: [PATCH 161/339] Use `OverlayColourProvider` and adjust metrics to roughly match new designs --- osu.Game/Skinning/Editor/SkinEditor.cs | 52 ++++++++++++++++++++------ 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 921849c0aa..b891d63da3 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -18,11 +18,12 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components.Menus; -using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osuTK; @@ -50,6 +51,9 @@ namespace osu.Game.Skinning.Editor [Resolved] private OsuColour colours { get; set; } + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + private bool hasBegunMutating; private Container content; @@ -314,7 +318,8 @@ namespace osu.Game.Skinning.Editor private class SceneLibrary : CompositeDrawable { - public const float HEIGHT = 30; + public const float BUTTON_HEIGHT = 40; + private const float padding = 10; [Resolved(canBeNull: true)] @@ -325,18 +330,18 @@ namespace osu.Game.Skinning.Editor public SceneLibrary() { - Height = HEIGHT + padding * 2; + Height = BUTTON_HEIGHT + padding * 2; } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider overlayColourProvider) { InternalChildren = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("222") + Colour = overlayColourProvider.Background5, }, new OsuScrollContainer(Direction.Horizontal) { @@ -353,11 +358,18 @@ namespace osu.Game.Skinning.Editor Direction = FillDirection.Horizontal, Children = new Drawable[] { - new PurpleTriangleButton + new OsuSpriteText + { + Text = "Scene library", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding(10), + }, + new SceneButton { Text = "Song Select", - Width = 100, - Height = HEIGHT, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Action = () => game?.PerformFromScreen(screen => { if (screen is SongSelect) @@ -366,11 +378,11 @@ namespace osu.Game.Skinning.Editor screen.Push(new PlaySongSelect()); }, new[] { typeof(SongSelect) }) }, - new PurpleTriangleButton + new SceneButton { Text = "Gameplay", - Width = 100, - Height = HEIGHT, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Action = () => game?.PerformFromScreen(screen => { if (screen is Player) @@ -387,6 +399,22 @@ namespace osu.Game.Skinning.Editor } }; } + + private class SceneButton : OsuButton + { + public SceneButton() + { + Width = 100; + Height = BUTTON_HEIGHT; + } + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) + { + BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3; + Content.CornerRadius = 5; + } + } } } } From b08d4bb8eb12c6d8f7337aa1445b821203cd1c95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 15:34:04 +0900 Subject: [PATCH 162/339] Move `SceneLibrary` implementation to its own file --- osu.Game/Skinning/Editor/SkinEditor.cs | 111 +--------------- .../Skinning/Editor/SkinEditorSceneLibrary.cs | 123 ++++++++++++++++++ 2 files changed, 124 insertions(+), 110 deletions(-) create mode 100644 osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index b891d63da3..acae09ee71 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -4,29 +4,21 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components.Menus; -using osu.Game.Screens.Play; -using osu.Game.Screens.Select; -using osuTK; namespace osu.Game.Skinning.Editor { @@ -119,7 +111,7 @@ namespace osu.Game.Skinning.Editor }, }, }, - new SceneLibrary + new SkinEditorSceneLibrary { RelativeSizeAxes = Axes.X, Y = menu_height, @@ -315,106 +307,5 @@ namespace osu.Game.Skinning.Editor foreach (var item in items) availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item); } - - private class SceneLibrary : CompositeDrawable - { - public const float BUTTON_HEIGHT = 40; - - private const float padding = 10; - - [Resolved(canBeNull: true)] - private OsuGame game { get; set; } - - [Resolved] - private IBindable ruleset { get; set; } - - public SceneLibrary() - { - Height = BUTTON_HEIGHT + padding * 2; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider overlayColourProvider) - { - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = overlayColourProvider.Background5, - }, - new OsuScrollContainer(Direction.Horizontal) - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new FillFlowContainer - { - Name = "Scene library", - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Spacing = new Vector2(padding), - Padding = new MarginPadding(padding), - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "Scene library", - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding(10), - }, - new SceneButton - { - Text = "Song Select", - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Action = () => game?.PerformFromScreen(screen => - { - if (screen is SongSelect) - return; - - screen.Push(new PlaySongSelect()); - }, new[] { typeof(SongSelect) }) - }, - new SceneButton - { - Text = "Gameplay", - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Action = () => game?.PerformFromScreen(screen => - { - if (screen is Player) - return; - - var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); - if (replayGeneratingMod != null) - screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); - }, new[] { typeof(Player), typeof(SongSelect) }) - }, - } - }, - } - } - }; - } - - private class SceneButton : OsuButton - { - public SceneButton() - { - Width = 100; - Height = BUTTON_HEIGHT; - } - - [BackgroundDependencyLoader(true)] - private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) - { - BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3; - Content.CornerRadius = 5; - } - } - } } } diff --git a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs new file mode 100644 index 0000000000..5da6147e4c --- /dev/null +++ b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs @@ -0,0 +1,123 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Screens.Play; +using osu.Game.Screens.Select; +using osuTK; + +namespace osu.Game.Skinning.Editor +{ + public class SkinEditorSceneLibrary : CompositeDrawable + { + public const float BUTTON_HEIGHT = 40; + + private const float padding = 10; + + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } + + public SkinEditorSceneLibrary() + { + Height = BUTTON_HEIGHT + padding * 2; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider overlayColourProvider) + { + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = overlayColourProvider.Background6, + }, + new OsuScrollContainer(Direction.Horizontal) + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new FillFlowContainer + { + Name = "Scene library", + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Spacing = new Vector2(padding), + Padding = new MarginPadding(padding), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Scene library", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding(10), + }, + new SceneButton + { + Text = "Song Select", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is SongSelect) + return; + + screen.Push(new PlaySongSelect()); + }, new[] { typeof(SongSelect) }) + }, + new SceneButton + { + Text = "Gameplay", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is Player) + return; + + var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); + if (replayGeneratingMod != null) + screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); + }, new[] { typeof(Player), typeof(SongSelect) }) + }, + } + }, + } + } + }; + } + + private class SceneButton : OsuButton + { + public SceneButton() + { + Width = 100; + Height = BUTTON_HEIGHT; + } + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) + { + BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3; + Content.CornerRadius = 5; + } + } + } +} From fdb411c0f3dcd9bf7ea7e5ec4a13028dac551160 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 17:19:20 +0900 Subject: [PATCH 163/339] Update skin editor toolbox design to suck less --- .../Skinning/Editor/SkinComponentToolbox.cs | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 2781f1958f..e5fe13b64d 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -6,16 +6,15 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; @@ -23,7 +22,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osuTK; -using osuTK.Graphics; namespace osu.Game.Skinning.Editor { @@ -33,8 +31,6 @@ namespace osu.Game.Skinning.Editor public Action RequestPlacement; - private const float component_display_scale = 0.8f; - [Cached] private ScoreProcessor scoreProcessor = new ScoreProcessor(new DummyRuleset()) { @@ -62,7 +58,7 @@ namespace osu.Game.Skinning.Editor RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(20) + Spacing = new Vector2(2) }; var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() @@ -120,39 +116,36 @@ namespace osu.Game.Skinning.Editor Enabled.Value = true; RelativeSizeAxes = Axes.X; - Height = 70; + Height = 60; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider, OsuColour colours) { - BackgroundColour = colours.Gray3; - Content.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = 2, - Offset = new Vector2(0, 1), - Colour = Color4.Black.Opacity(0.5f) - }; + BackgroundColour = colourProvider.Background3; AddRange(new Drawable[] { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10) { Bottom = 20 }, + Masking = true, + Child = innerContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = component + }, + }, new OsuSpriteText { Text = component.GetType().Name, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding(5), }, - innerContainer = new Container - { - Y = 10, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(component_display_scale), - Masking = true, - Child = component - } }); // adjust provided component to fit / display in a known state. @@ -160,14 +153,17 @@ namespace osu.Game.Skinning.Editor component.Origin = Anchor.Centre; } - protected override void LoadComplete() + protected override void Update() { - base.LoadComplete(); + base.Update(); - if (component.RelativeSizeAxes != Axes.None) + if (component.DrawSize != Vector2.Zero) { - innerContainer.AutoSizeAxes = Axes.None; - innerContainer.Height = 100; + float bestScale = Math.Min( + innerContainer.DrawWidth / component.DrawWidth, + innerContainer.DrawHeight / component.DrawHeight); + + innerContainer.Scale = new Vector2(bestScale); } } From e4a6b7ae9139ec2b8600accc717fcad1e525a3cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 17:33:06 +0900 Subject: [PATCH 164/339] Expand toolbox component items on hover --- .../Skinning/Editor/SkinComponentToolbox.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index e5fe13b64d..3980163a9d 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -109,6 +109,9 @@ namespace osu.Game.Skinning.Editor private Container innerContainer; + private const float contracted_size = 60; + private const float expanded_size = 120; + public ToolboxComponentButton(Drawable component) { this.component = component; @@ -116,7 +119,19 @@ namespace osu.Game.Skinning.Editor Enabled.Value = true; RelativeSizeAxes = Axes.X; - Height = 60; + Height = contracted_size; + } + + protected override bool OnHover(HoverEvent e) + { + this.Delay(300).ResizeHeightTo(expanded_size, 500, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + this.ResizeHeightTo(contracted_size, 500, Easing.OutQuint); } [BackgroundDependencyLoader] From 59cb1ac126bed54c93ccc600cc134696d7d82482 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 17:35:25 +0900 Subject: [PATCH 165/339] Order components by name for now --- osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 3980163a9d..98fdc7c93f 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -64,6 +64,7 @@ namespace osu.Game.Skinning.Editor var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() .Where(t => !t.IsInterface) .Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)) + .OrderBy(t => t.Name) .ToArray(); foreach (var type in skinnableTypes) From 4525ed645c4c844c21774eeb6b2ced2e0028c3e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:36:04 +0900 Subject: [PATCH 166/339] Update skin editor to use `EditorSidebar` --- .../TestSceneSkinEditorComponentsList.cs | 12 ++++++--- .../Skinning/Editor/SkinComponentToolbox.cs | 12 +++------ osu.Game/Skinning/Editor/SkinEditor.cs | 27 +++++++++++++------ .../Skinning/Editor/SkinSettingsToolbox.cs | 20 ++++++++------ 4 files changed, 44 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs index 58c89411c0..5385a9983b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Skinning.Editor; @@ -11,13 +13,17 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinEditorComponentsList : SkinnableTestScene { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + [Test] public void TestToggleEditor() { - AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox(300) + AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 0.6f, })); } diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 2781f1958f..05da229c3e 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -18,19 +18,17 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Components; using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning.Editor { - public class SkinComponentToolbox : ScrollingToolboxGroup + public class SkinComponentToolbox : EditorSidebarSection { - public const float WIDTH = 200; - public Action RequestPlacement; private const float component_display_scale = 0.8f; @@ -45,11 +43,9 @@ namespace osu.Game.Skinning.Editor [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); - public SkinComponentToolbox(float height) - : base("Components", height) + public SkinComponentToolbox() + : base("Components") { - RelativeSizeAxes = Axes.None; - Width = WIDTH; } [BackgroundDependencyLoader] diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index acae09ee71..381f7a1cc3 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Skinning.Editor @@ -130,21 +131,31 @@ namespace osu.Game.Skinning.Editor { new Drawable[] { - new SkinComponentToolbox(600) + new EditorSidebar { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RequestPlacement = placeComponent + Children = new[] + { + new SkinComponentToolbox + { + RequestPlacement = placeComponent + }, + } }, content = new Container { RelativeSizeAxes = Axes.Both, }, - settingsToolbox = new SkinSettingsToolbox + new EditorSidebar { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } + Children = new[] + { + settingsToolbox = new SkinSettingsToolbox + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + } + }, } } } diff --git a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs b/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs index c0ef8e7316..fc06d3647a 100644 --- a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs @@ -2,22 +2,26 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Rulesets.Edit; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Edit.Components; using osuTK; namespace osu.Game.Skinning.Editor { - internal class SkinSettingsToolbox : ScrollingToolboxGroup + internal class SkinSettingsToolbox : EditorSidebarSection { - public const float WIDTH = 200; + protected override Container Content { get; } public SkinSettingsToolbox() - : base("Settings", 600) + : base("Settings") { - RelativeSizeAxes = Axes.None; - Width = WIDTH; - - FillFlow.Spacing = new Vector2(10); + base.Content.Add(Content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + }); } } } From 54e351efe9e6dfe53fe13034108a454ff480ab03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:57:39 +0900 Subject: [PATCH 167/339] Convert top level skin editor layout to use grid container Fix `SkinEditor`'s initial target not being a `Screen` --- osu.Game/Skinning/Editor/SkinEditor.cs | 180 +++++++++++++------------ 1 file changed, 95 insertions(+), 85 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 381f7a1cc3..f7e5aeecdf 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -17,7 +17,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; @@ -51,7 +50,7 @@ namespace osu.Game.Skinning.Editor private Container content; - private EditorToolboxGroup settingsToolbox; + private EditorSidebarSection settingsToolbox; public SkinEditor() { @@ -72,92 +71,111 @@ namespace osu.Game.Skinning.Editor InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Child = new GridContainer { - new Container + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - Name = "Menu container", - RelativeSizeAxes = Axes.X, - Depth = float.MinValue, - Height = menu_height, - Children = new Drawable[] - { - new EditorMenuBar - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Items = new[] - { - new MenuItem("File") - { - Items = new[] - { - new EditorMenuItem("Save", MenuItemType.Standard, Save), - new EditorMenuItem("Revert to default", MenuItemType.Destructive, revert), - new EditorMenuItemSpacer(), - new EditorMenuItem("Exit", MenuItemType.Standard, Hide), - }, - }, - } - }, - headerText = new OsuTextFlowContainer - { - TextAnchor = Anchor.TopRight, - Padding = new MarginPadding(5), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - }, - }, - }, - new SkinEditorSceneLibrary - { - RelativeSizeAxes = Axes.X, - Y = menu_height, + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), }, - new GridContainer + Content = new[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new Drawable[] { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] + new Container { - new EditorSidebar + Name = "Menu container", + RelativeSizeAxes = Axes.X, + Depth = float.MinValue, + Height = menu_height, + Children = new Drawable[] { - Children = new[] + new EditorMenuBar { - new SkinComponentToolbox + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Items = new[] { - RequestPlacement = placeComponent + new MenuItem("File") + { + Items = new[] + { + new EditorMenuItem("Save", MenuItemType.Standard, Save), + new EditorMenuItem("Revert to default", MenuItemType.Destructive, revert), + new EditorMenuItemSpacer(), + new EditorMenuItem("Exit", MenuItemType.Standard, Hide), + }, + }, + } + }, + headerText = new OsuTextFlowContainer + { + TextAnchor = Anchor.TopRight, + Padding = new MarginPadding(5), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + }, + }, + }, + }, + new Drawable[] + { + new SkinEditorSceneLibrary + { + RelativeSizeAxes = Axes.X, + }, + }, + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + new EditorSidebar + { + Children = new[] + { + new SkinComponentToolbox + { + RequestPlacement = placeComponent + }, + } + }, + content = new Container + { + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both, + }, + new EditorSidebar + { + Children = new[] + { + settingsToolbox = new SkinSettingsToolbox + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + } }, } - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - }, - new EditorSidebar - { - Children = new[] - { - settingsToolbox = new SkinSettingsToolbox - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } - } - }, + } } - } + }, } } }; @@ -191,17 +209,9 @@ namespace osu.Game.Skinning.Editor SelectedComponents.Clear(); Scheduler.AddOnce(loadBlueprintContainer); + Scheduler.AddOnce(populateSettings); - void loadBlueprintContainer() - { - content.Children = new Drawable[] - { - new SkinBlueprintContainer(targetScreen) - { - Margin = new MarginPadding { Top = 100 }, - } - }; - } + void loadBlueprintContainer() => content.Child = new SkinBlueprintContainer(targetScreen); } private void skinChanged() From 27122c17c91e1b2e0e5cc14a5f13c37d6123ef64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 17:41:20 +0900 Subject: [PATCH 168/339] Show settings for multiple components in a selection --- osu.Game/Skinning/Editor/SkinEditor.cs | 25 ++++--------------- .../Skinning/Editor/SkinSettingsToolbox.cs | 7 ++++-- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index f7e5aeecdf..6fe3df6e8a 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Testing; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; @@ -50,7 +49,7 @@ namespace osu.Game.Skinning.Editor private Container content; - private EditorSidebarSection settingsToolbox; + private EditorSidebar settingsSidebar; public SkinEditor() { @@ -161,17 +160,7 @@ namespace osu.Game.Skinning.Editor Depth = float.MaxValue, RelativeSizeAxes = Axes.Both, }, - new EditorSidebar - { - Children = new[] - { - settingsToolbox = new SkinSettingsToolbox - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } - } - }, + settingsSidebar = new EditorSidebar(), } } } @@ -266,14 +255,10 @@ namespace osu.Game.Skinning.Editor private void populateSettings() { - settingsToolbox.Clear(); + settingsSidebar.Clear(); - var first = SelectedComponents.OfType().FirstOrDefault(); - - if (first != null) - { - settingsToolbox.Children = first.CreateSettingsControls().ToArray(); - } + foreach (var component in SelectedComponents.OfType()) + settingsSidebar.Add(new SkinSettingsToolbox(component)); } private IEnumerable availableTargets => targetScreen.ChildrenOfType(); diff --git a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs b/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs index fc06d3647a..d2823ed0e4 100644 --- a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinSettingsToolbox.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.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; using osu.Game.Screens.Edit.Components; using osuTK; @@ -12,8 +14,8 @@ namespace osu.Game.Skinning.Editor { protected override Container Content { get; } - public SkinSettingsToolbox() - : base("Settings") + public SkinSettingsToolbox(Drawable component) + : base($"Settings ({component.GetType().Name})") { base.Content.Add(Content = new FillFlowContainer { @@ -21,6 +23,7 @@ namespace osu.Game.Skinning.Editor AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(10), + Children = component.CreateSettingsControls().ToArray() }); } } From cc356bcfe4afacb6392e22bdfa665cf4f3249cc9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 18:57:53 +0900 Subject: [PATCH 169/339] Show components available for current screen only (using actual live dependencies) --- .../Skinning/Editor/SkinComponentToolbox.cs | 76 ++++++++----------- osu.Game/Skinning/Editor/SkinEditor.cs | 31 ++++---- 2 files changed, 47 insertions(+), 60 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 483e365e78..9c4a369c1d 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -2,24 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Utils; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Components; using osuTK; @@ -29,26 +21,19 @@ namespace osu.Game.Skinning.Editor { public Action RequestPlacement; - [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(new DummyRuleset()) - { - Combo = { Value = RNG.Next(1, 1000) }, - TotalScore = { Value = RNG.Next(1000, 10000000) } - }; + private readonly CompositeDrawable target; - [Cached(typeof(HealthProcessor))] - private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); - - public SkinComponentToolbox() + public SkinComponentToolbox(CompositeDrawable target = null) : base("Components") { + this.target = target; } + private FillFlowContainer fill; + [BackgroundDependencyLoader] private void load() { - FillFlowContainer fill; - Child = fill = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -57,6 +42,13 @@ namespace osu.Game.Skinning.Editor Spacing = new Vector2(2) }; + reloadComponents(); + } + + private void reloadComponents() + { + fill.Clear(); + var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() .Where(t => !t.IsInterface) .Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)) @@ -64,18 +56,10 @@ namespace osu.Game.Skinning.Editor .ToArray(); foreach (var type in skinnableTypes) - { - var component = attemptAddComponent(type); - - if (component != null) - { - component.RequestPlacement = t => RequestPlacement?.Invoke(t); - fill.Add(component); - } - } + attemptAddComponent(type); } - private static ToolboxComponentButton attemptAddComponent(Type type) + private void attemptAddComponent(Type type) { try { @@ -83,14 +67,15 @@ namespace osu.Game.Skinning.Editor Debug.Assert(instance != null); - if (!((ISkinnableDrawable)instance).IsEditable) - return null; + if (!((ISkinnableDrawable)instance).IsEditable) return; - return new ToolboxComponentButton(instance); + fill.Add(new ToolboxComponentButton(instance, target) + { + RequestPlacement = t => RequestPlacement?.Invoke(t) + }); } catch { - return null; } } @@ -101,6 +86,7 @@ namespace osu.Game.Skinning.Editor public override bool PropagateNonPositionalInputSubTree => false; private readonly Drawable component; + private readonly CompositeDrawable dependencySource; public Action RequestPlacement; @@ -109,9 +95,10 @@ namespace osu.Game.Skinning.Editor private const float contracted_size = 60; private const float expanded_size = 120; - public ToolboxComponentButton(Drawable component) + public ToolboxComponentButton(Drawable component, CompositeDrawable dependencySource) { this.component = component; + this.dependencySource = dependencySource; Enabled.Value = true; @@ -143,7 +130,7 @@ namespace osu.Game.Skinning.Editor RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(10) { Bottom = 20 }, Masking = true, - Child = innerContainer = new Container + Child = innerContainer = new DependencyBorrowingContainer(dependencySource) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -186,14 +173,17 @@ namespace osu.Game.Skinning.Editor } } - private class DummyRuleset : Ruleset + public class DependencyBorrowingContainer : Container { - public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); - public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); - public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException(); - public override string Description => string.Empty; - public override string ShortName => string.Empty; + private readonly CompositeDrawable donor; + + public DependencyBorrowingContainer(CompositeDrawable donor) + { + this.donor = donor; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => + new DependencyContainer(donor?.Dependencies ?? base.CreateChildDependencies(parent)); } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index f7e5aeecdf..2a7e3439c0 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -51,6 +51,7 @@ namespace osu.Game.Skinning.Editor private Container content; private EditorSidebarSection settingsToolbox; + private EditorSidebar componentsSidebar; public SkinEditor() { @@ -146,16 +147,7 @@ namespace osu.Game.Skinning.Editor { new Drawable[] { - new EditorSidebar - { - Children = new[] - { - new SkinComponentToolbox - { - RequestPlacement = placeComponent - }, - } - }, + componentsSidebar = new EditorSidebar(), content = new Container { Depth = float.MaxValue, @@ -211,7 +203,15 @@ namespace osu.Game.Skinning.Editor Scheduler.AddOnce(loadBlueprintContainer); Scheduler.AddOnce(populateSettings); - void loadBlueprintContainer() => content.Child = new SkinBlueprintContainer(targetScreen); + void loadBlueprintContainer() + { + content.Child = new SkinBlueprintContainer(targetScreen); + + componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable) + { + RequestPlacement = placeComponent + }; + } } private void skinChanged() @@ -238,12 +238,7 @@ namespace osu.Game.Skinning.Editor private void placeComponent(Type type) { - var target = availableTargets.FirstOrDefault()?.Target; - - if (target == null) - return; - - var targetContainer = getTarget(target.Value); + var targetContainer = getFirstTarget(); if (targetContainer == null) return; @@ -278,6 +273,8 @@ namespace osu.Game.Skinning.Editor private IEnumerable availableTargets => targetScreen.ChildrenOfType(); + private ISkinnableTarget getFirstTarget() => availableTargets.FirstOrDefault(); + private ISkinnableTarget getTarget(SkinnableTarget target) { return availableTargets.FirstOrDefault(c => c.Target == target); From 7e5262364525dc4cd0685b3ef8bc4287e8092b74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 20:02:52 +0900 Subject: [PATCH 170/339] Add "Reset position" menu item in skin editor --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index bd6d097eb2..46755728a7 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; @@ -199,6 +200,12 @@ namespace osu.Game.Skinning.Editor Items = createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray() }; + yield return new OsuMenuItem("Reset position", MenuItemType.Standard, () => + { + foreach (var blueprint in SelectedBlueprints) + ((Drawable)blueprint.Item).Position = Vector2.Zero; + }); + foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; From c2e4779c2ed429a5e54dd405938e80347f1d2bab Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 16 Mar 2022 11:57:11 +0000 Subject: [PATCH 171/339] Remove redundant spacing in `ControlItem` test scene --- osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index e496ef60d7..8289be033a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -91,7 +91,6 @@ namespace osu.Game.Tests.Visual.Online flow = new FillFlowContainer { Direction = FillDirection.Vertical, - Spacing = new Vector2(20), RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }, From 3f61b0d9688e99221c51522563be6aee7939c5aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 22:23:19 +0900 Subject: [PATCH 172/339] Add missing `OverlayColourProvider` to `SkinEditor` test --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 9012492028..e74345aae9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Skinning.Editor; @@ -16,6 +18,9 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool Autoplay => true; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + [SetUpSteps] public override void SetUpSteps() { From 03d3969b38ebd37e03e8b09c4f6eed5218f4766d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Mar 2022 21:46:02 +0100 Subject: [PATCH 173/339] Remove unnecessary `OsuColour` cache One is already cached at `OsuGameBase` level. --- osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 8289be033a..3465e7dfec 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -8,7 +8,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; @@ -24,9 +23,6 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); - [Cached] - private readonly OsuColour osuColour = new OsuColour(); - [Cached] private readonly Bindable selected = new Bindable(); From a8cefca685f9ec5faed81112a70f894c838f7f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Mar 2022 21:49:04 +0100 Subject: [PATCH 174/339] Simplify fade-out transform --- osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 3465e7dfec..e66092bf27 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual.Online private void leaveChannel(Channel channel) { leaveText.Text = $"OnRequestLeave: {channel.Name}"; - leaveText.FadeIn().Then().FadeOut(1000, Easing.InQuint); + leaveText.FadeOutFromOne(1000, Easing.InQuint); } private static Channel createPublicChannel(string name) => From b21fa78cbfa57b749138fdcd36a256ea727bc610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Mar 2022 21:55:36 +0100 Subject: [PATCH 175/339] Move dependencies out of fields to BDL args where possible --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 5 +---- .../Overlays/Chat/ChannelControl/ControlItemMention.cs | 8 +------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index cc00550965..c27d5fdf5d 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -33,16 +33,13 @@ namespace osu.Game.Overlays.Chat.ChannelControl [Resolved] private Bindable selectedChannel { get; set; } = null!; - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - public ControlItem(Channel channel) { this.channel = channel; } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { Height = 30; RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index beb2713c92..34e1e33c87 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -20,14 +20,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl private OsuSpriteText? countText; - [Resolved] - private OsuColour osuColour { get; set; } = null!; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - [BackgroundDependencyLoader] - private void load() + private void load(OsuColour osuColour, OverlayColourProvider colourProvider) { Masking = true; Size = new Vector2(20, 12); From 1a04260807bfaf54675e753b4a0b7561d7349fad Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 05:51:12 +0300 Subject: [PATCH 176/339] Update mod instantiaton utility method to no longer check for validity --- osu.Game/Utils/ModUtils.cs | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index d5ea74c404..bccb706842 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -153,31 +153,17 @@ namespace osu.Game.Utils } /// - /// Verifies all proposed mods are valid for a given ruleset and returns instantiated s for further processing. + /// Returns an instantiated list of all proposed mods on a given ruleset. /// - /// The ruleset to verify mods against. + /// The ruleset to instantiate mods. /// The proposed mods. - /// Mods instantiated from which were valid for the given . - /// Whether all were valid for the given . - public static bool InstantiateValidModsForRuleset(Ruleset ruleset, IEnumerable proposedMods, out List valid) + /// Mods instantiated from on the given . + public static void InstantiateModsForRuleset(Ruleset ruleset, IEnumerable proposedMods, out List mods) { - valid = new List(); - bool proposedWereValid = true; + mods = new List(); foreach (var apiMod in proposedMods) - { - try - { - // will throw if invalid - valid.Add(apiMod.ToMod(ruleset)); - } - catch - { - proposedWereValid = false; - } - } - - return proposedWereValid; + mods.Add(apiMod.ToMod(ruleset)); } } } From 83189d1f07dc21af413f74f48e38c52082d6d005 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 06:24:55 +0300 Subject: [PATCH 177/339] Revert "Update mod instantiaton utility method to no longer check for validity" This reverts commit 1a04260807bfaf54675e753b4a0b7561d7349fad. --- osu.Game/Utils/ModUtils.cs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index bccb706842..d5ea74c404 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -153,17 +153,31 @@ namespace osu.Game.Utils } /// - /// Returns an instantiated list of all proposed mods on a given ruleset. + /// Verifies all proposed mods are valid for a given ruleset and returns instantiated s for further processing. /// - /// The ruleset to instantiate mods. + /// The ruleset to verify mods against. /// The proposed mods. - /// Mods instantiated from on the given . - public static void InstantiateModsForRuleset(Ruleset ruleset, IEnumerable proposedMods, out List mods) + /// Mods instantiated from which were valid for the given . + /// Whether all were valid for the given . + public static bool InstantiateValidModsForRuleset(Ruleset ruleset, IEnumerable proposedMods, out List valid) { - mods = new List(); + valid = new List(); + bool proposedWereValid = true; foreach (var apiMod in proposedMods) - mods.Add(apiMod.ToMod(ruleset)); + { + try + { + // will throw if invalid + valid.Add(apiMod.ToMod(ruleset)); + } + catch + { + proposedWereValid = false; + } + } + + return proposedWereValid; } } } From e0a06bf5d931ceffb4ff0a42d4269483896d99df Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 06:28:10 +0300 Subject: [PATCH 178/339] Update mod instantiation utility method inline with `APIMod.ToMod` changes --- osu.Game/Utils/ModUtils.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index d5ea74c404..ff8e04cc58 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -166,15 +166,15 @@ namespace osu.Game.Utils foreach (var apiMod in proposedMods) { - try - { - // will throw if invalid - valid.Add(apiMod.ToMod(ruleset)); - } - catch + var mod = apiMod.ToMod(ruleset); + + if (mod is UnknownMod) { proposedWereValid = false; + continue; } + + valid.Add(mod); } return proposedWereValid; From 1eac0f41bf4e624e32d125de2635561a8ff01e14 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Mar 2022 13:44:54 +0900 Subject: [PATCH 179/339] Remove unused using --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 46755728a7..d7fb5c0498 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; From 46e66e66e477507c3c7564930b2c615ed81f47da Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 08:19:38 +0300 Subject: [PATCH 180/339] Make opening chat overlay opt-in to add coverage for unloaded chat overlays Causes `TestHighlightWhileChatNeverOpen` to fail as expected. --- .../Visual/Online/TestSceneChatOverlay.cs | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 00ff6a9576..80a6698761 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -122,6 +122,8 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestHideOverlay() { + AddStep("Open chat overlay", () => chatOverlay.Show()); + AddAssert("Chat overlay is visible", () => chatOverlay.State.Value == Visibility.Visible); AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); @@ -134,6 +136,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChannelSelection() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); AddStep("Setup get message response", () => onGetMessages = channel => { @@ -169,6 +172,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestSearchInSelector() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Search for 'no. 2'", () => chatOverlay.ChildrenOfType().First().Text = "no. 2"); AddUntilStep("Only channel 2 visible", () => { @@ -180,6 +184,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChannelShortcutKeys() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join channels", () => channels.ForEach(channel => channelManager.JoinChannel(channel))); AddStep("Close channel selector", () => InputManager.Key(Key.Escape)); AddUntilStep("Wait for close", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); @@ -199,6 +204,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestCloseChannelBehaviour() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddUntilStep("Join until dropdown has channels", () => { if (visibleChannels.Count() < joinedChannels.Count()) @@ -269,6 +275,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChannelCloseButton() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join 2 channels", () => { channelManager.JoinChannel(channel1); @@ -289,6 +296,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestCloseTabShortcut() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join 2 channels", () => { channelManager.JoinChannel(channel1); @@ -314,6 +322,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestNewTabShortcut() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join 2 channels", () => { channelManager.JoinChannel(channel1); @@ -330,6 +339,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestRestoreTabShortcut() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join 3 channels", () => { channelManager.JoinChannel(channel1); @@ -375,6 +385,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChatCommand() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); @@ -398,6 +409,8 @@ namespace osu.Game.Tests.Visual.Online { Channel multiplayerChannel = null; + AddStep("open chat overlay", () => chatOverlay.Show()); + AddStep("join multiplayer channel", () => channelManager.JoinChannel(multiplayerChannel = new Channel(new APIUser()) { Name = "#mp_1", @@ -417,6 +430,7 @@ namespace osu.Game.Tests.Visual.Online { Message message = null; + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); @@ -443,6 +457,7 @@ namespace osu.Game.Tests.Visual.Online { Message message = null; + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); @@ -471,6 +486,8 @@ namespace osu.Game.Tests.Visual.Online { Message message = null; + AddStep("Open chat overlay", () => chatOverlay.Show()); + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); @@ -496,14 +513,11 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestHighlightWhileChatHidden() + public void TestHighlightWhileChatNeverOpen() { Message message = null; - AddStep("hide chat", () => chatOverlay.Hide()); - AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); AddStep("Send message in channel 1", () => { @@ -520,7 +534,7 @@ namespace osu.Game.Tests.Visual.Online }); }); - AddStep("Highlight message and show chat", () => + AddStep("Highlight message and open chat", () => { chatOverlay.HighlightMessage(message, channel1); chatOverlay.Show(); @@ -571,8 +585,6 @@ namespace osu.Game.Tests.Visual.Online ChannelManager, ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, }, }; - - ChatOverlay.Show(); } } From 7aae9bbd1b33943fab4301934db6d3d20322660e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 08:31:38 +0300 Subject: [PATCH 181/339] Improve channel bindable logic in `ChatOverlay` to avoid potential nullrefs --- osu.Game/Overlays/ChatOverlay.cs | 38 ++++++++++++-------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 3764ac42fc..3d39c7ce3a 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -73,6 +73,10 @@ namespace osu.Game.Overlays private Container channelSelectionContainer; protected ChannelSelectionOverlay ChannelSelectionOverlay; + private readonly IBindableList availableChannels = new BindableList(); + private readonly IBindableList joinedChannels = new BindableList(); + private readonly Bindable currentChannel = new Bindable(); + public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) || (ChannelSelectionOverlay.State.Value == Visibility.Visible && ChannelSelectionOverlay.ReceivePositionalInputAt(screenSpacePos)); @@ -198,9 +202,13 @@ namespace osu.Game.Overlays }, }; + availableChannels.BindTo(channelManager.AvailableChannels); + joinedChannels.BindTo(channelManager.JoinedChannels); + currentChannel.BindTo(channelManager.CurrentChannel); + textBox.OnCommit += postMessage; - ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; + ChannelTabControl.Current.ValueChanged += current => currentChannel.Value = current.NewValue; ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; ChannelSelectionOverlay.State.ValueChanged += state => { @@ -238,18 +246,12 @@ namespace osu.Game.Overlays Schedule(() => { // TODO: consider scheduling bindable callbacks to not perform when overlay is not present. - channelManager.JoinedChannels.BindCollectionChanged(joinedChannelsChanged, true); - - channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; - availableChannelsChanged(null, null); - - currentChannel = channelManager.CurrentChannel.GetBoundCopy(); + joinedChannels.BindCollectionChanged(joinedChannelsChanged, true); + availableChannels.BindCollectionChanged(availableChannelsChanged, true); currentChannel.BindValueChanged(currentChannelChanged, true); }); } - private Bindable currentChannel; - private void currentChannelChanged(ValueChangedEvent e) { if (e.NewValue == null) @@ -318,7 +320,7 @@ namespace osu.Game.Overlays if (!channel.Joined.Value) channel = channelManager.JoinChannel(channel); - channelManager.CurrentChannel.Value = channel; + currentChannel.Value = channel; } channel.HighlightedMessage.Value = message; @@ -407,7 +409,7 @@ namespace osu.Game.Overlays return true; case PlatformAction.DocumentClose: - channelManager.LeaveChannel(channelManager.CurrentChannel.Value); + channelManager.LeaveChannel(currentChannel.Value); return true; } @@ -487,19 +489,7 @@ namespace osu.Game.Overlays private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) { - ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (channelManager != null) - { - channelManager.CurrentChannel.ValueChanged -= currentChannelChanged; - channelManager.JoinedChannels.CollectionChanged -= joinedChannelsChanged; - channelManager.AvailableChannels.CollectionChanged -= availableChannelsChanged; - } + ChannelSelectionOverlay.UpdateAvailableChannels(availableChannels); } private void postMessage(TextBox textBox, bool newText) From ac739c9dae7a6ab6ca09eac1e9a9b93eeeac5ce4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 22:55:47 +0900 Subject: [PATCH 182/339] Change `PerformancePointsCounter` resolution requirements to be required All other similar UI components have required dependencies, so this is mainly to bring things in line with expectations. I am using this fact in the skin editor to only show components which can be used in the current editor context (by `try-catch`ing their `Activator.CreateInstance`). --- .../Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 5 +++++ osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 4 ++++ .../Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs | 4 ++++ .../Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs | 4 ++++ osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 6 ++---- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index c5ea9e6204..c70313ee8a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -18,9 +18,11 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Storyboards; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Gameplay { @@ -37,6 +39,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + protected override bool HasCustomSteps => true; [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 505f73159f..d6a4d9bf79 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Skinning; +using osu.Game.Tests.Beatmaps; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -30,6 +31,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + // best way to check without exposing. private Drawable hideTarget => hudOverlay.KeyCounter; private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 8f33f6fac5..5dc4bdb041 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Skinning.Editor; +using osu.Game.Tests.Beatmaps; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -22,6 +23,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + [SetUpSteps] public void SetUpSteps() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index cdf349ff7f..da443a6c92 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -29,6 +30,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + private IEnumerable hudOverlays => CreatedDrawables.OfType(); // best way to check without exposing. diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index c0d0ea0721..7a1f724cfb 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -42,12 +42,10 @@ namespace osu.Game.Screens.Play.HUD private const float alpha_when_invalid = 0.3f; - [CanBeNull] - [Resolved(CanBeNull = true)] + [Resolved] private ScoreProcessor scoreProcessor { get; set; } - [Resolved(CanBeNull = true)] - [CanBeNull] + [Resolved] private GameplayState gameplayState { get; set; } [CanBeNull] From fd71aa4a4d321078f0da1194db47f18590052c8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Mar 2022 15:05:51 +0900 Subject: [PATCH 183/339] Change `SongProgress` resolution requirements to be required --- osu.Game/Screens/Play/SongProgress.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index b27a9c5f5d..694b5ec628 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -73,9 +73,12 @@ namespace osu.Game.Screens.Play [Resolved(canBeNull: true)] private Player player { get; set; } - [Resolved(canBeNull: true)] + [Resolved] private GameplayClock gameplayClock { get; set; } + [Resolved] + private DrawableRuleset drawableRuleset { get; set; } + private IClock referenceClock; public bool UsesFixedAnchor { get; set; } @@ -113,7 +116,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, OsuConfigManager config, DrawableRuleset drawableRuleset) + private void load(OsuColour colours, OsuConfigManager config) { base.LoadComplete(); From 93bc0ac86966bed11fd59ce02df600e4cf717844 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Mar 2022 17:49:35 +0900 Subject: [PATCH 184/339] Update download links in README.md for macOS (to add Apple Silicon) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f64240f67a..dba0b2670d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ If you are looking to install or test osu! without setting up a development envi **Latest build:** -| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.15+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) +| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | ------------- | ------------- | ------------- | ------------- | ------------- | - The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. From 7b8fb341a54e9a5dab83bf363aec810cc466782e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Mar 2022 17:59:26 +0900 Subject: [PATCH 185/339] Fix not handling IconButtons --- osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index b7aa8af4aa..2deb8686cc 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -127,9 +127,9 @@ namespace osu.Game.Tests.Visual where T : Drawable { if (typeof(T) == typeof(Button)) - AddUntilStep($"wait for {typeof(T).Name} enabled", () => (this.ChildrenOfType().Single() as Button)?.Enabled.Value == true); + AddUntilStep($"wait for {typeof(T).Name} enabled", () => (this.ChildrenOfType().Single() as ClickableContainer)?.Enabled.Value == true); else - AddUntilStep($"wait for {typeof(T).Name} enabled", () => this.ChildrenOfType().Single().ChildrenOfType