From 2071cba944050ce4da997b1e8111d96650d12c22 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 13 Nov 2020 12:32:23 -0800 Subject: [PATCH 001/394] Add music bindings to on screen display --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- .../Overlays/Music/MusicKeyBindingHandler.cs | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a4b99bb6e6..89a6ee8b07 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -196,7 +196,7 @@ namespace osu.Game.Configuration public Func LookupSkinName { private get; set; } - public Func LookupKeyBindings { private get; set; } + public Func LookupKeyBindings { get; set; } } public enum OsuSetting diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index e6edfb1e3e..0d6158d46f 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Overlays.OSD; @@ -25,6 +26,9 @@ namespace osu.Game.Overlays.Music [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } + [Resolved] + private OsuConfigManager config { get; set; } + public bool OnPressed(GlobalAction action) { if (beatmap.Disabled) @@ -37,11 +41,11 @@ namespace osu.Game.Overlays.Music bool wasPlaying = musicController.IsPlaying; if (musicController.TogglePause()) - onScreenDisplay?.Display(new MusicActionToast(wasPlaying ? "Pause track" : "Play track")); + onScreenDisplay?.Display(new MusicActionToast(wasPlaying ? "Pause track" : "Play track", config.LookupKeyBindings(action))); return true; case GlobalAction.MusicNext: - musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast("Next track"))); + musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast("Next track", config.LookupKeyBindings(action)))); return true; @@ -51,11 +55,11 @@ namespace osu.Game.Overlays.Music switch (res) { case PreviousTrackResult.Restart: - onScreenDisplay?.Display(new MusicActionToast("Restart track")); + onScreenDisplay?.Display(new MusicActionToast("Restart track", config.LookupKeyBindings(action))); break; case PreviousTrackResult.Previous: - onScreenDisplay?.Display(new MusicActionToast("Previous track")); + onScreenDisplay?.Display(new MusicActionToast("Previous track", config.LookupKeyBindings(action))); break; } }); @@ -72,8 +76,8 @@ namespace osu.Game.Overlays.Music private class MusicActionToast : Toast { - public MusicActionToast(string action) - : base("Music Playback", action, string.Empty) + public MusicActionToast(string action, string shortcut) + : base("Music Playback", action, shortcut) { } } From 9d8e7e895442c29df40a3ebf2fb0df5cf020bbb1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Nov 2020 04:46:26 +0300 Subject: [PATCH 002/394] ProfileLineChart layout implementation --- .../Online/TestSceneProfileLineChart.cs | 39 +++++++++++ .../Sections/Historical/ProfileLineChart.cs | 64 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs create mode 100644 osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs new file mode 100644 index 0000000000..34359baab5 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Overlays.Profile.Sections.Historical; +using osu.Framework.Graphics; +using static osu.Game.Users.User; +using System; +using osu.Game.Overlays; +using osu.Framework.Allocation; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneProfileLineChart : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + + public TestSceneProfileLineChart() + { + var values = new[] + { + new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1000 }, + new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 20 }, + new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 20000 }, + new UserHistoryCount { Date = new DateTime(2010, 8, 1), Count = 30 }, + new UserHistoryCount { Date = new DateTime(2010, 9, 1), Count = 50 }, + new UserHistoryCount { Date = new DateTime(2010, 10, 1), Count = 2000 }, + new UserHistoryCount { Date = new DateTime(2010, 11, 1), Count = 2100 } + }; + + Add(new ProfileLineChart + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Values = values + }); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs new file mode 100644 index 0000000000..10a03aa012 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using JetBrains.Annotations; +using static osu.Game.Users.User; + +namespace osu.Game.Overlays.Profile.Sections.Historical +{ + public class ProfileLineChart : CompositeDrawable + { + private UserHistoryCount[] values; + + [CanBeNull] + public UserHistoryCount[] Values + { + get => values; + set + { + values = value; + graph.Values = values; + } + } + + private readonly UserHistoryGraph graph; + + public ProfileLineChart() + { + RelativeSizeAxes = Axes.X; + Height = 250; + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + Empty(), + graph = new UserHistoryGraph + { + RelativeSizeAxes = Axes.Both + } + }, + new Drawable[] + { + Empty(), + Empty() + } + } + }; + } + } +} From d98c59f2a4b399da80b5c511f062c60c6d5d536e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Nov 2020 06:38:02 +0300 Subject: [PATCH 003/394] Implement horizontal ticks creation --- .../Sections/Historical/ProfileLineChart.cs | 126 +++++++++++++++++- 1 file changed, 123 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 10a03aa012..a02f869f51 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -5,6 +5,13 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using JetBrains.Annotations; using static osu.Game.Users.User; +using System; +using System.Linq; +using osu.Game.Graphics.Sprites; +using osu.Framework.Utils; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Framework.Graphics.Shapes; namespace osu.Game.Overlays.Profile.Sections.Historical { @@ -20,10 +27,14 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { values = value; graph.Values = values; + + createRowTicks(); } } private readonly UserHistoryGraph graph; + private readonly Container rowTicksContainer; + private readonly Container rowLinesContainer; public ProfileLineChart() { @@ -46,10 +57,25 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { new Drawable[] { - Empty(), - graph = new UserHistoryGraph + rowTicksContainer = new Container { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + rowLinesContainer = new Container + { + RelativeSizeAxes = Axes.Both + }, + graph = new UserHistoryGraph + { + RelativeSizeAxes = Axes.Both + } + } } }, new Drawable[] @@ -60,5 +86,99 @@ namespace osu.Game.Overlays.Profile.Sections.Historical } }; } + + private void createRowTicks() + { + rowTicksContainer.Clear(); + rowLinesContainer.Clear(); + + var min = values.Select(v => v.Count).Min(); + var max = values.Select(v => v.Count).Max(); + + var niceRange = niceNumber(max - min, false); + var niceTick = niceNumber(niceRange / (6 - 1), true); + var axisStart = Math.Floor(min / niceTick) * niceTick; + var axisEnd = Math.Ceiling(max / niceTick) * niceTick; + + var rollingRow = axisStart; + + while (rollingRow <= axisEnd) + { + var y = -Interpolation.ValueAt(rollingRow, 0, 1f, min, max); + + rowTicksContainer.Add(new TickText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.CentreRight, + RelativePositionAxes = Axes.Y, + Text = rollingRow.ToString("N0"), + Y = y + }); + + rowLinesContainer.Add(new TickLine + { + Anchor = Anchor.BottomRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.X, + RelativePositionAxes = Axes.Y, + Height = 1, + Y = y + }); + + rollingRow += niceTick; + } + } + + private double niceNumber(double value, bool round) + { + var exponent = (int)Math.Floor(Math.Log10(value)); + var fraction = value / Math.Pow(10, exponent); + + double niceFraction; + + if (round) + { + if (fraction < 1.5) + niceFraction = 1.0; + else if (fraction < 3) + niceFraction = 2.0; + else if (fraction < 7) + niceFraction = 5.0; + else + niceFraction = 10.0; + } + else + { + if (fraction <= 1.0) + niceFraction = 1.0; + else if (fraction <= 2.0) + niceFraction = 2.0; + else if (fraction <= 5.0) + niceFraction = 5.0; + else + niceFraction = 10.0; + } + + return niceFraction * Math.Pow(10, exponent); + } + + private class TickText : OsuSpriteText + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Colour = colourProvider.Foreground1; + Font = OsuFont.GetFont(size: 12); + } + } + + private class TickLine : Box + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Colour = colourProvider.Background6; + } + } } } From 00e974794076c241779694f6ec988a8ccf204044 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Nov 2020 06:44:29 +0300 Subject: [PATCH 004/394] Test scene visual improvements --- .../Online/TestSceneProfileLineChart.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs index 34359baab5..0be835c07d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs @@ -7,6 +7,8 @@ using static osu.Game.Users.User; using System; using osu.Game.Overlays; using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Containers; namespace osu.Game.Tests.Visual.Online { @@ -28,11 +30,25 @@ namespace osu.Game.Tests.Visual.Online new UserHistoryCount { Date = new DateTime(2010, 11, 1), Count = 2100 } }; - Add(new ProfileLineChart + AddRange(new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Values = values + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4 + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding { Horizontal = 50 }, + Child = new ProfileLineChart + { + Values = values + } + } }); } } From 01f28a35c3269316e5c380d14c8c41c3a65eff6b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Nov 2020 07:28:01 +0300 Subject: [PATCH 005/394] Implement vertical ticks creation --- .../Sections/Historical/ProfileLineChart.cs | 70 +++++++++++++++++-- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index a02f869f51..2908b50a6e 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -29,12 +29,15 @@ namespace osu.Game.Overlays.Profile.Sections.Historical graph.Values = values; createRowTicks(); + createColumnTicks(); } } private readonly UserHistoryGraph graph; private readonly Container rowTicksContainer; + private readonly Container columnTicksContainer; private readonly Container rowLinesContainer; + private readonly Container columnLinesContainer; public ProfileLineChart() { @@ -67,9 +70,20 @@ namespace osu.Game.Overlays.Profile.Sections.Historical RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - rowLinesContainer = new Container + new Container { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Children = new[] + { + rowLinesContainer = new Container + { + RelativeSizeAxes = Axes.Both + }, + columnLinesContainer = new Container + { + RelativeSizeAxes = Axes.Both + } + } }, graph = new UserHistoryGraph { @@ -81,7 +95,12 @@ namespace osu.Game.Overlays.Profile.Sections.Historical new Drawable[] { Empty(), - Empty() + columnTicksContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Top = 10 } + } } } }; @@ -104,7 +123,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical while (rollingRow <= axisEnd) { - var y = -Interpolation.ValueAt(rollingRow, 0, 1f, min, max); + var y = -Interpolation.ValueAt(rollingRow, 0, 1f, axisStart, axisEnd); rowTicksContainer.Add(new TickText { @@ -129,9 +148,50 @@ namespace osu.Game.Overlays.Profile.Sections.Historical } } + private void createColumnTicks() + { + columnTicksContainer.Clear(); + columnLinesContainer.Clear(); + + var min = values.Select(v => v.Date).Min().Ticks; + var max = values.Select(v => v.Date).Max().Ticks; + + var niceRange = niceNumber(max - min, false); + var niceTick = niceNumber(niceRange / (Math.Min(values.Length, 15) - 1), true); + var axisStart = Math.Floor(min / niceTick) * niceTick; + var axisEnd = Math.Ceiling(max / niceTick) * niceTick; + + var rollingRow = axisStart; + + while (rollingRow <= axisEnd) + { + var x = Interpolation.ValueAt(rollingRow, 0, 1f, axisStart, axisEnd); + + columnTicksContainer.Add(new TickText + { + Origin = Anchor.CentreLeft, + RelativePositionAxes = Axes.X, + Text = new DateTime((long)rollingRow).ToString("MMM yyyy"), + Rotation = 45, + X = x + }); + + columnLinesContainer.Add(new TickLine + { + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + RelativePositionAxes = Axes.X, + Width = 1, + X = x + }); + + rollingRow += niceTick; + } + } + private double niceNumber(double value, bool round) { - var exponent = (int)Math.Floor(Math.Log10(value)); + var exponent = Math.Floor(Math.Log10(value)); var fraction = value / Math.Pow(10, exponent); double niceFraction; From ae4a2e74faab435156cfebe6f6da131b5b83896b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Nov 2020 18:21:10 +0300 Subject: [PATCH 006/394] Implement ProfileSubsection --- ...cs => TestSceneProfileSubsectionHeader.cs} | 6 +- .../Profile/Sections/PaginatedContainer.cs | 57 +++----------- .../Profile/Sections/ProfileSubsection.cs | 78 +++++++++++++++++++ ...erHeader.cs => ProfileSubsectionHeader.cs} | 4 +- 4 files changed, 95 insertions(+), 50 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestScenePaginatedContainerHeader.cs => TestSceneProfileSubsectionHeader.cs} (95%) create mode 100644 osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs rename osu.Game/Overlays/Profile/Sections/{PaginatedContainerHeader.cs => ProfileSubsectionHeader.cs} (95%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs similarity index 95% rename from osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs index 2e9f919cfd..cd226662d7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs @@ -11,12 +11,12 @@ using osu.Framework.Allocation; namespace osu.Game.Tests.Visual.UserInterface { - public class TestScenePaginatedContainerHeader : OsuTestScene + public class TestSceneProfileSubsectionHeader : OsuTestScene { [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); - private PaginatedContainerHeader header; + private ProfileSubsectionHeader header; [Test] public void TestHiddenCounter() @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void createHeader(string text, CounterVisibilityState state, int initialValue = 0) { Clear(); - Add(header = new PaginatedContainerHeader(text, state) + Add(header = new ProfileSubsectionHeader(text, state) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index c1107ce907..7b66c3f51e 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -6,10 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.API; -using osu.Game.Rulesets; using osu.Game.Users; using System.Collections.Generic; using System.Linq; @@ -18,7 +15,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Profile.Sections { - public abstract class PaginatedContainer : FillFlowContainer + public abstract class PaginatedContainer : ProfileSubsection { [Resolved] private IAPIProvider api { get; set; } @@ -26,42 +23,25 @@ namespace osu.Game.Overlays.Profile.Sections protected int VisiblePages; protected int ItemsPerPage; - protected readonly Bindable User = new Bindable(); protected FillFlowContainer ItemsContainer; - protected RulesetStore Rulesets; private APIRequest> retrievalRequest; private CancellationTokenSource loadCancellation; - private readonly string missingText; private ShowMoreButton moreButton; - private OsuSpriteText missing; - private PaginatedContainerHeader header; - - private readonly string headerText; - private readonly CounterVisibilityState counterVisibilityState; protected PaginatedContainer(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + : base(user, headerText, missingText, counterVisibilityState) { - this.headerText = headerText; - this.missingText = missingText; - this.counterVisibilityState = counterVisibilityState; - User.BindTo(user); } - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) + protected override Drawable CreateContent() => new FillFlowContainer { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; - + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new PaginatedContainerHeader(headerText, counterVisibilityState) - { - Alpha = string.IsNullOrEmpty(headerText) ? 0 : 1 - }, ItemsContainer = new FillFlowContainer { AutoSizeAxes = Axes.Y, @@ -76,21 +56,10 @@ namespace osu.Game.Overlays.Profile.Sections Margin = new MarginPadding { Top = 10 }, Action = showMore, }, - missing = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 15), - Text = missingText, - Alpha = 0, - }, - }; + } + }; - Rulesets = rulesets; - - User.ValueChanged += onUserChanged; - User.TriggerChange(); - } - - private void onUserChanged(ValueChangedEvent e) + protected override void OnUserChanged(ValueChangedEvent e) { loadCancellation?.Cancel(); retrievalRequest?.Cancel(); @@ -124,15 +93,15 @@ namespace osu.Game.Overlays.Profile.Sections moreButton.Hide(); moreButton.IsLoading = false; - if (!string.IsNullOrEmpty(missing.Text)) - missing.Show(); + if (!string.IsNullOrEmpty(Missing.Text)) + Missing.Show(); return; } LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null), drawables => { - missing.Hide(); + Missing.Hide(); moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); moreButton.IsLoading = false; @@ -142,8 +111,6 @@ namespace osu.Game.Overlays.Profile.Sections protected virtual int GetCount(User user) => 0; - protected void SetCount(int value) => header.Current.Value = value; - protected virtual void OnItemsReceived(List items) { } diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs new file mode 100644 index 0000000000..9583759693 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Users; +using JetBrains.Annotations; + +namespace osu.Game.Overlays.Profile.Sections +{ + public abstract class ProfileSubsection : FillFlowContainer + { + protected readonly Bindable User = new Bindable(); + + protected RulesetStore Rulesets { get; private set; } + + protected OsuSpriteText Missing { get; private set; } + + private readonly string headerText; + private readonly string missingText; + private readonly CounterVisibilityState counterVisibilityState; + + private ProfileSubsectionHeader header; + + protected ProfileSubsection(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + { + this.headerText = headerText; + this.missingText = missingText; + this.counterVisibilityState = counterVisibilityState; + User.BindTo(user); + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + + Children = new Drawable[] + { + header = new ProfileSubsectionHeader(headerText, counterVisibilityState) + { + Alpha = string.IsNullOrEmpty(headerText) ? 0 : 1 + }, + CreateContent(), + Missing = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 15), + Text = missingText, + Alpha = 0, + }, + }; + + Rulesets = rulesets; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + User.BindValueChanged(OnUserChanged, true); + } + + [NotNull] + protected abstract Drawable CreateContent(); + + protected virtual void OnUserChanged(ValueChangedEvent e) + { + } + + protected void SetCount(int value) => header.Current.Value = value; + } +} diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs similarity index 95% rename from osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs rename to osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs index 8c617e5fbd..5858cebe89 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics; namespace osu.Game.Overlays.Profile.Sections { - public class PaginatedContainerHeader : CompositeDrawable, IHasCurrentValue + public class ProfileSubsectionHeader : CompositeDrawable, IHasCurrentValue { private readonly BindableWithCurrent current = new BindableWithCurrent(); @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Profile.Sections private CounterPill counterPill; - public PaginatedContainerHeader(string text, CounterVisibilityState counterState) + public ProfileSubsectionHeader(string text, CounterVisibilityState counterState) { this.text = text; this.counterState = counterState; From af174aa653206d3ae3dbb42368b2c4ff5285a488 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Nov 2020 18:48:47 +0300 Subject: [PATCH 007/394] Implement chart subsections --- .../Historical/ChartProfileSubsection.cs | 51 +++++++++++++++++++ .../Historical/PlayHistorySubsection.cs | 19 +++++++ .../Sections/Historical/ProfileLineChart.cs | 4 +- .../Sections/Historical/ReplaysSubsection.cs | 19 +++++++ .../Profile/Sections/HistoricalSection.cs | 2 + 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs create mode 100644 osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs create mode 100644 osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs new file mode 100644 index 0000000000..24083c9a79 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Users; +using static osu.Game.Users.User; + +namespace osu.Game.Overlays.Profile.Sections.Historical +{ + public abstract class ChartProfileSubsection : ProfileSubsection + { + private ProfileLineChart chart; + + protected ChartProfileSubsection(Bindable user, string headerText) + : base(user, headerText) + { + + } + + protected override Drawable CreateContent() => new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Top = 10, + Left = 20, + Right = 40 + }, + Child = chart = new ProfileLineChart() + }; + + protected override void OnUserChanged(ValueChangedEvent e) + { + var values = GetValues(e.NewValue); + + if (values?.Length > 1) + { + chart.Values = values; + Show(); + return; + } + + Hide(); + } + + protected abstract UserHistoryCount[] GetValues(User user); + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs new file mode 100644 index 0000000000..3e35f80b49 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Users; +using static osu.Game.Users.User; + +namespace osu.Game.Overlays.Profile.Sections.Historical +{ + public class PlayHistorySubsection : ChartProfileSubsection + { + public PlayHistorySubsection(Bindable user) + : base(user, "Play History") + { + } + + protected override UserHistoryCount[] GetValues(User user) => user.MonthlyPlaycounts; + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 2908b50a6e..55fa6c5400 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -130,7 +130,9 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Anchor = Anchor.BottomRight, Origin = Anchor.CentreRight, RelativePositionAxes = Axes.Y, + Margin = new MarginPadding { Right = 3 }, Text = rollingRow.ToString("N0"), + Font = OsuFont.GetFont(size: 12), Y = y }); @@ -172,6 +174,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Origin = Anchor.CentreLeft, RelativePositionAxes = Axes.X, Text = new DateTime((long)rollingRow).ToString("MMM yyyy"), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Rotation = 45, X = x }); @@ -228,7 +231,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private void load(OverlayColourProvider colourProvider) { Colour = colourProvider.Foreground1; - Font = OsuFont.GetFont(size: 12); } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs new file mode 100644 index 0000000000..f6abd1c4fc --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Users; +using static osu.Game.Users.User; + +namespace osu.Game.Overlays.Profile.Sections.Historical +{ + public class ReplaysSubsection : ChartProfileSubsection + { + public ReplaysSubsection(Bindable user) + : base(user, "Replays Watched History") + { + } + + protected override UserHistoryCount[] GetValues(User user) => user.ReplaysWatchedCounts; + } +} diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index bfc47bd88c..6e2b9873cf 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -18,8 +18,10 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new Drawable[] { + new PlayHistorySubsection(User), new PaginatedMostPlayedBeatmapContainer(User), new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", CounterVisibilityState.VisibleWhenZero), + new ReplaysSubsection(User) }; } } From 02168c6c2fb7f31907c5d02797fbc1508075dd87 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Nov 2020 19:17:01 +0300 Subject: [PATCH 008/394] Implement dates with zero count fill --- .../Historical/ChartProfileSubsection.cs | 28 ++++++++++++++++++- .../Sections/Historical/ProfileLineChart.cs | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs index 24083c9a79..38224dd177 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -38,7 +39,32 @@ namespace osu.Game.Overlays.Profile.Sections.Historical if (values?.Length > 1) { - chart.Values = values; + // Fill dates with 0 count + + var newValues = new List { values[0] }; + var newLast = values[0]; + + for (int i = 1; i < values.Length; i++) + { + while (hasMissingDates(newLast, values[i])) + { + newValues.Add(newLast = new UserHistoryCount + { + Count = 0, + Date = newLast.Date.AddMonths(1) + }); + } + + newValues.Add(newLast = values[i]); + } + + static bool hasMissingDates(UserHistoryCount prev, UserHistoryCount current) + { + var possibleCurrent = prev.Date.AddMonths(1); + return possibleCurrent != current.Date; + } + + chart.Values = newValues.ToArray(); Show(); return; } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 55fa6c5400..7cd529a726 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { private UserHistoryCount[] values; - [CanBeNull] + [NotNull] public UserHistoryCount[] Values { get => values; From 5354bf1fa593bcbfb1d6cafac3e4143af035e45e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Nov 2020 20:07:52 +0300 Subject: [PATCH 009/394] Ticks distribution improvements --- .../Sections/Historical/ProfileLineChart.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 7cd529a726..fe4677037b 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -116,14 +116,12 @@ namespace osu.Game.Overlays.Profile.Sections.Historical var niceRange = niceNumber(max - min, false); var niceTick = niceNumber(niceRange / (6 - 1), true); - var axisStart = Math.Floor(min / niceTick) * niceTick; - var axisEnd = Math.Ceiling(max / niceTick) * niceTick; - var rollingRow = axisStart; + double rollingRow = min; - while (rollingRow <= axisEnd) + while (rollingRow <= max) { - var y = -Interpolation.ValueAt(rollingRow, 0, 1f, axisStart, axisEnd); + var y = -Interpolation.ValueAt(rollingRow, 0, 1f, min, max); rowTicksContainer.Add(new TickText { @@ -155,25 +153,23 @@ namespace osu.Game.Overlays.Profile.Sections.Historical columnTicksContainer.Clear(); columnLinesContainer.Clear(); - var min = values.Select(v => v.Date).Min().Ticks; - var max = values.Select(v => v.Date).Max().Ticks; + var min = values.Select(v => v.Date).Min().ToOADate(); + var max = values.Select(v => v.Date).Max().ToOADate(); var niceRange = niceNumber(max - min, false); var niceTick = niceNumber(niceRange / (Math.Min(values.Length, 15) - 1), true); - var axisStart = Math.Floor(min / niceTick) * niceTick; - var axisEnd = Math.Ceiling(max / niceTick) * niceTick; - var rollingRow = axisStart; + double rollingRow = min; - while (rollingRow <= axisEnd) + while (rollingRow <= max) { - var x = Interpolation.ValueAt(rollingRow, 0, 1f, axisStart, axisEnd); + var x = Interpolation.ValueAt(rollingRow, 0, 1f, min, max); columnTicksContainer.Add(new TickText { Origin = Anchor.CentreLeft, RelativePositionAxes = Axes.X, - Text = new DateTime((long)rollingRow).ToString("MMM yyyy"), + Text = DateTime.FromOADate(rollingRow).ToString("MMM yyyy"), Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Rotation = 45, X = x From a94546f905ca8ca42e9b4386c148bf362bf612a4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Nov 2020 20:17:32 +0300 Subject: [PATCH 010/394] CI fixes --- .../Profile/Sections/Historical/ChartProfileSubsection.cs | 1 - .../Overlays/Profile/Sections/Historical/ProfileLineChart.cs | 2 +- osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs index 38224dd177..4445cdce51 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs @@ -17,7 +17,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical protected ChartProfileSubsection(Bindable user, string headerText) : base(user, headerText) { - } protected override Drawable CreateContent() => new Container diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index fe4677037b..b7983fd356 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical } } }, - new Drawable[] + new[] { Empty(), columnTicksContainer = new Container diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs index 9583759693..751b35e342 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Profile.Sections AutoSizeAxes = Axes.Y; Direction = FillDirection.Vertical; - Children = new Drawable[] + Children = new[] { header = new ProfileSubsectionHeader(headerText, counterVisibilityState) { From fe9d17fc568cf757991573fb55d12f36875ed908 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Nov 2020 20:31:03 +0300 Subject: [PATCH 011/394] Fix CodeFactor issues --- osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs | 2 +- .../Overlays/Profile/Sections/Historical/ProfileLineChart.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs index 0be835c07d..3d342b0d76 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs @@ -3,12 +3,12 @@ using osu.Game.Overlays.Profile.Sections.Historical; using osu.Framework.Graphics; -using static osu.Game.Users.User; using System; using osu.Game.Overlays; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Containers; +using static osu.Game.Users.User; namespace osu.Game.Tests.Visual.Online { diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index b7983fd356..5a9c42d7e0 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -4,7 +4,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using JetBrains.Annotations; -using static osu.Game.Users.User; using System; using System.Linq; using osu.Game.Graphics.Sprites; @@ -12,6 +11,7 @@ using osu.Framework.Utils; using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Framework.Graphics.Shapes; +using static osu.Game.Users.User; namespace osu.Game.Overlays.Profile.Sections.Historical { From a52c98b55cc0be3e190610a9214cca09eb8288a0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Nov 2020 21:20:37 +0300 Subject: [PATCH 012/394] Fix broken test scene --- .../Profile/Sections/Historical/PlayHistorySubsection.cs | 2 +- .../Overlays/Profile/Sections/Historical/ReplaysSubsection.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs index 3e35f80b49..2f15886c3a 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs @@ -14,6 +14,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { } - protected override UserHistoryCount[] GetValues(User user) => user.MonthlyPlaycounts; + protected override UserHistoryCount[] GetValues(User user) => user?.MonthlyPlaycounts; } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs index f6abd1c4fc..e594e8d020 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs @@ -14,6 +14,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { } - protected override UserHistoryCount[] GetValues(User user) => user.ReplaysWatchedCounts; + protected override UserHistoryCount[] GetValues(User user) => user?.ReplaysWatchedCounts; } } From a4b20d211726e0980d55969e2193121cb9b60b75 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 15 Nov 2020 13:00:07 +0800 Subject: [PATCH 013/394] Make EZ mod able to fail in Taiko --- osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 36 ++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index c51b47dc6e..7ac01d2aa7 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -1,12 +1,44 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using Humanizer; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Graphics; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModEasy : ModEasy + public class TaikoModEasy : Mod, IApplicableToDifficulty, IApplicableFailOverride { - public override string Description => @"Beats move slower, less accuracy required, and three lives!"; + public override string Name => "Easy"; + public override string Acronym => "EZ"; + public override string Description => @"Beats move slower, less accuracy required"; + public override IconUsage? Icon => OsuIcon.ModEasy; + public override ModType Type => ModType.DifficultyReduction; + public override double ScoreMultiplier => 0.5; + public override bool Ranked => true; + public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) }; + + public void ReadFromDifficulty(BeatmapDifficulty difficulty) + { + } + + public void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + const float ratio = 0.5f; + difficulty.CircleSize *= ratio; + difficulty.ApproachRate *= ratio; + difficulty.DrainRate *= ratio; + difficulty.OverallDifficulty *= ratio; + } + + public bool PerformFail() => true; + + public bool RestartOnFail => false; + } } From cf7ac6d5e38aa036cbea9b14fc618f607dd29de1 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 15 Nov 2020 13:38:21 +0800 Subject: [PATCH 014/394] Remove import --- osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index 7ac01d2aa7..383623ca0a 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -2,11 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Humanizer; -using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; From 3406b0d74f3de4e56bcea4ef8be2a737671e6e85 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 15 Nov 2020 14:54:04 +0800 Subject: [PATCH 015/394] Fix checks --- osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index 383623ca0a..c3b833fa29 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -36,6 +36,5 @@ namespace osu.Game.Rulesets.Taiko.Mods public bool PerformFail() => true; public bool RestartOnFail => false; - } } From dc38aeac4392d44b1b99aa3b4a18e47aa5373f21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Nov 2020 17:23:02 +0900 Subject: [PATCH 016/394] Remove unnecessary local definition of colour logic from taiko judgement --- .../UI/DrawableTaikoJudgement.cs | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index cbfc5a8628..b5e35f88b5 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Rulesets.Objects.Drawables; -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Judgements; using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.UI { @@ -25,21 +22,6 @@ namespace osu.Game.Rulesets.Taiko.UI { } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - switch (Result.Type) - { - case HitResult.Ok: - JudgementBody.Colour = colours.GreenLight; - break; - - case HitResult.Great: - JudgementBody.Colour = colours.BlueLight; - break; - } - } - protected override void ApplyHitAnimations() { this.MoveToY(-100, 500); From d7acfd5413cb1844b55f3f858a2b97147d8787cc Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Mon, 16 Nov 2020 18:15:15 +0800 Subject: [PATCH 017/394] Remove retires from ModEasy --- osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs | 42 +++++++++++++++++ osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs | 47 +++++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs | 49 +++++++++++++++++++- osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 33 ++----------- osu.Game/Rulesets/Mods/ModEasy.cs | 44 ++---------------- 5 files changed, 144 insertions(+), 71 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs index a82d0af102..f58a4051de 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs @@ -1,12 +1,54 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using Humanizer; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Catch.Mods { public class CatchModEasy : ModEasy { public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!"; + + [SettingSource("Extra Lives", "Number of extra lives")] + public Bindable Retries { get; } = new BindableInt(2) + { + MinValue = 0, + MaxValue = 10 + }; + public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}"; + + private int retries; + + private BindableNumber health; + + public override void ReadFromDifficulty(BeatmapDifficulty difficulty) + { + } + + public override void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + const float ratio = 0.5f; + difficulty.CircleSize *= ratio; + difficulty.ApproachRate *= ratio; + difficulty.DrainRate *= ratio; + difficulty.OverallDifficulty *= ratio; + + retries = Retries.Value; + } + public bool PerformFail() + { + if (retries == 0) return true; + + health.Value = health.MaxValue; + retries--; + + return false; + } + public bool RestartOnFail => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs index ff77df0ae0..52c959395f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs @@ -1,12 +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 Humanizer; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModEasy : ModEasy { public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!"; + + [SettingSource("Extra Lives", "Number of extra lives")] + public Bindable Retries { get; } = new BindableInt(2) + { + MinValue = 0, + MaxValue = 10 + }; + public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}"; + + private int retries; + + private BindableNumber health; + + public override void ReadFromDifficulty(BeatmapDifficulty difficulty) + { + } + + public override void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + const float ratio = 0.5f; + difficulty.CircleSize *= ratio; + difficulty.ApproachRate *= ratio; + difficulty.DrainRate *= ratio; + difficulty.OverallDifficulty *= ratio; + + retries = Retries.Value; + } + public bool PerformFail() + { + if (retries == 0) return true; + + health.Value = health.MaxValue; + retries--; + + return false; + } + public bool RestartOnFail => false; + + public void ApplyToHealthProcessor(HealthProcessor healthProcessor) + { + health = healthProcessor.Health.GetBoundCopy(); + } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs index f13c7d2ff6..df7f7bbce6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs @@ -1,12 +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 Humanizer; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModEasy : ModEasy + public class OsuModEasy : ModEasy, IApplicableToDifficulty, IApplicableFailOverride, IApplicableToHealthProcessor { public override string Description => @"Larger circles, more forgiving HP drain, less accuracy required, and three lives!"; + + [SettingSource("Extra Lives", "Number of extra lives")] + public Bindable Retries { get; } = new BindableInt(2) + { + MinValue = 0, + MaxValue = 10 + }; + public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}"; + + private int retries; + + private BindableNumber health; + + public override void ReadFromDifficulty(BeatmapDifficulty difficulty) + { + } + + public override void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + const float ratio = 0.5f; + difficulty.CircleSize *= ratio; + difficulty.ApproachRate *= ratio; + difficulty.DrainRate *= ratio; + difficulty.OverallDifficulty *= ratio; + + retries = Retries.Value; + } + public bool PerformFail() + { + if (retries == 0) return true; + + health.Value = health.MaxValue; + retries--; + + return false; + } + public bool RestartOnFail => false; + + public void ApplyToHealthProcessor(HealthProcessor healthProcessor) + { + health = healthProcessor.Health.GetBoundCopy(); + } } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index c3b833fa29..9e49c3fa13 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -1,40 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Mods; +using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModEasy : Mod, IApplicableToDifficulty, IApplicableFailOverride + public class TaikoModEasy : ModEasy { - public override string Name => "Easy"; - public override string Acronym => "EZ"; - public override string Description => @"Beats move slower, less accuracy required"; - public override IconUsage? Icon => OsuIcon.ModEasy; - public override ModType Type => ModType.DifficultyReduction; - public override double ScoreMultiplier => 0.5; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) }; - - public void ReadFromDifficulty(BeatmapDifficulty difficulty) - { - } - - public void ApplyToDifficulty(BeatmapDifficulty difficulty) - { - const float ratio = 0.5f; - difficulty.CircleSize *= ratio; - difficulty.ApproachRate *= ratio; - difficulty.DrainRate *= ratio; - difficulty.OverallDifficulty *= ratio; - } - - public bool PerformFail() => true; - - public bool RestartOnFail => false; + public override string Description => @"Beats move slower, less accuracy required!"; } } diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index c6f3930029..cf06091d99 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -2,17 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Humanizer; -using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModEasy : Mod, IApplicableToDifficulty, IApplicableFailOverride, IApplicableToHealthProcessor + public abstract class ModEasy : Mod, IApplicableToDifficulty { public override string Name => "Easy"; public override string Acronym => "EZ"; @@ -22,49 +18,17 @@ namespace osu.Game.Rulesets.Mods public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) }; - [SettingSource("Extra Lives", "Number of extra lives")] - public Bindable Retries { get; } = new BindableInt(2) - { - MinValue = 0, - MaxValue = 10 - }; - - public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}"; - - private int retries; - - private BindableNumber health; - - public void ReadFromDifficulty(BeatmapDifficulty difficulty) + public virtual void ReadFromDifficulty(BeatmapDifficulty difficulty) { } - public void ApplyToDifficulty(BeatmapDifficulty difficulty) + public virtual void ApplyToDifficulty(BeatmapDifficulty difficulty) { const float ratio = 0.5f; difficulty.CircleSize *= ratio; difficulty.ApproachRate *= ratio; difficulty.DrainRate *= ratio; - difficulty.OverallDifficulty *= ratio; - - retries = Retries.Value; - } - - public bool PerformFail() - { - if (retries == 0) return true; - - health.Value = health.MaxValue; - retries--; - - return false; - } - - public bool RestartOnFail => false; - - public void ApplyToHealthProcessor(HealthProcessor healthProcessor) - { - health = healthProcessor.Health.GetBoundCopy(); + difficulty.OverallDifficulty *= ratio;; } } } From f2ef7bee5d48c2946cebcbcd401d9ed1d6d69288 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Mon, 16 Nov 2020 18:17:50 +0800 Subject: [PATCH 018/394] Fix checks --- osu.Game/Rulesets/Mods/ModEasy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index cf06091d99..1290e8136c 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mods difficulty.CircleSize *= ratio; difficulty.ApproachRate *= ratio; difficulty.DrainRate *= ratio; - difficulty.OverallDifficulty *= ratio;; + difficulty.OverallDifficulty *= ratio; } } } From 017a6b71531f9d4efe35b2d3c97ddfc1f6274a0c Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Mon, 16 Nov 2020 18:22:17 +0800 Subject: [PATCH 019/394] Fix checks --- osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs | 5 +++++ osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs index f58a4051de..6e154c9202 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs @@ -50,5 +50,10 @@ namespace osu.Game.Rulesets.Catch.Mods return false; } public bool RestartOnFail => false; + + public void ApplyToHealthProcessor(HealthProcessor healthProcessor) + { + health = healthProcessor.Health.GetBoundCopy(); + } } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index 9e49c3fa13..0ec24412e9 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; -using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Taiko.Mods { From 99ee5e3ad74a50bc21855987fcb7640c8ef21d85 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Mon, 16 Nov 2020 18:28:50 +0800 Subject: [PATCH 020/394] Correct inheritance --- osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs | 2 +- osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs index 6e154c9202..88d20d15e2 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModEasy : ModEasy + public class CatchModEasy : ModEasy, IApplicableFailOverride, IApplicableToHealthProcessor { public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!"; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs index 52c959395f..79073b26ba 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModEasy : ModEasy + public class ManiaModEasy : ModEasy, IApplicableFailOverride, IApplicableToHealthProcessor { public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!"; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs index df7f7bbce6..43ceddd6e1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModEasy : ModEasy, IApplicableToDifficulty, IApplicableFailOverride, IApplicableToHealthProcessor + public class OsuModEasy : ModEasy, IApplicableFailOverride, IApplicableToHealthProcessor { public override string Description => @"Larger circles, more forgiving HP drain, less accuracy required, and three lives!"; From 84b42f207dc8beefd61122dad234674a196b20a0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Nov 2020 23:14:20 +0900 Subject: [PATCH 021/394] Fix triangles disappearing after a while --- .../Objects/Drawables/Pieces/TrianglesPiece.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs index 6cdb0d3df3..add62192f0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs @@ -7,8 +7,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class TrianglesPiece : Triangles { - protected override bool ExpireOffScreenTriangles => false; - protected override bool CreateNewTriangles => false; protected override float SpawnRatio => 0.5f; public TrianglesPiece(int? seed = null) From 8da40ce2dc8904b4d9937f6b179eb77cd988a455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 16 Nov 2020 19:42:08 +0100 Subject: [PATCH 022/394] Reduce duplication by extracting ModEasyWithExtraLives --- osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs | 49 +----------------- osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs | 49 +----------------- osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs | 49 +----------------- .../Rulesets/Mods/ModEasyWithExtraLives.cs | 50 +++++++++++++++++++ 4 files changed, 53 insertions(+), 144 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs index 88d20d15e2..16ef56d845 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs @@ -1,59 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using Humanizer; -using osu.Framework.Bindables; -using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModEasy : ModEasy, IApplicableFailOverride, IApplicableToHealthProcessor + public class CatchModEasy : ModEasyWithExtraLives { public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!"; - - [SettingSource("Extra Lives", "Number of extra lives")] - public Bindable Retries { get; } = new BindableInt(2) - { - MinValue = 0, - MaxValue = 10 - }; - public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}"; - - private int retries; - - private BindableNumber health; - - public override void ReadFromDifficulty(BeatmapDifficulty difficulty) - { - } - - public override void ApplyToDifficulty(BeatmapDifficulty difficulty) - { - const float ratio = 0.5f; - difficulty.CircleSize *= ratio; - difficulty.ApproachRate *= ratio; - difficulty.DrainRate *= ratio; - difficulty.OverallDifficulty *= ratio; - - retries = Retries.Value; - } - public bool PerformFail() - { - if (retries == 0) return true; - - health.Value = health.MaxValue; - retries--; - - return false; - } - public bool RestartOnFail => false; - - public void ApplyToHealthProcessor(HealthProcessor healthProcessor) - { - health = healthProcessor.Health.GetBoundCopy(); - } } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs index 79073b26ba..4093aeb2a7 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs @@ -1,59 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using Humanizer; -using osu.Framework.Bindables; -using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModEasy : ModEasy, IApplicableFailOverride, IApplicableToHealthProcessor + public class ManiaModEasy : ModEasyWithExtraLives { public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!"; - - [SettingSource("Extra Lives", "Number of extra lives")] - public Bindable Retries { get; } = new BindableInt(2) - { - MinValue = 0, - MaxValue = 10 - }; - public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}"; - - private int retries; - - private BindableNumber health; - - public override void ReadFromDifficulty(BeatmapDifficulty difficulty) - { - } - - public override void ApplyToDifficulty(BeatmapDifficulty difficulty) - { - const float ratio = 0.5f; - difficulty.CircleSize *= ratio; - difficulty.ApproachRate *= ratio; - difficulty.DrainRate *= ratio; - difficulty.OverallDifficulty *= ratio; - - retries = Retries.Value; - } - public bool PerformFail() - { - if (retries == 0) return true; - - health.Value = health.MaxValue; - retries--; - - return false; - } - public bool RestartOnFail => false; - - public void ApplyToHealthProcessor(HealthProcessor healthProcessor) - { - health = healthProcessor.Health.GetBoundCopy(); - } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs index 43ceddd6e1..06b5b6cfb8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs @@ -1,59 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using Humanizer; -using osu.Framework.Bindables; -using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModEasy : ModEasy, IApplicableFailOverride, IApplicableToHealthProcessor + public class OsuModEasy : ModEasyWithExtraLives { public override string Description => @"Larger circles, more forgiving HP drain, less accuracy required, and three lives!"; - - [SettingSource("Extra Lives", "Number of extra lives")] - public Bindable Retries { get; } = new BindableInt(2) - { - MinValue = 0, - MaxValue = 10 - }; - public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}"; - - private int retries; - - private BindableNumber health; - - public override void ReadFromDifficulty(BeatmapDifficulty difficulty) - { - } - - public override void ApplyToDifficulty(BeatmapDifficulty difficulty) - { - const float ratio = 0.5f; - difficulty.CircleSize *= ratio; - difficulty.ApproachRate *= ratio; - difficulty.DrainRate *= ratio; - difficulty.OverallDifficulty *= ratio; - - retries = Retries.Value; - } - public bool PerformFail() - { - if (retries == 0) return true; - - health.Value = health.MaxValue; - retries--; - - return false; - } - public bool RestartOnFail => false; - - public void ApplyToHealthProcessor(HealthProcessor healthProcessor) - { - health = healthProcessor.Health.GetBoundCopy(); - } } } diff --git a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs new file mode 100644 index 0000000000..2ac0f59d84 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModEasyWithExtraLives : ModEasy, IApplicableFailOverride, IApplicableToHealthProcessor + { + [SettingSource("Extra Lives", "Number of extra lives")] + public Bindable Retries { get; } = new BindableInt(2) + { + MinValue = 0, + MaxValue = 10 + }; + + public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}"; + + private int retries; + + private BindableNumber health; + + public override void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + base.ApplyToDifficulty(difficulty); + retries = Retries.Value; + } + + public bool PerformFail() + { + if (retries == 0) return true; + + health.Value = health.MaxValue; + retries--; + + return false; + } + + public bool RestartOnFail => false; + + public void ApplyToHealthProcessor(HealthProcessor healthProcessor) + { + health = healthProcessor.Health.GetBoundCopy(); + } + } +} From 80d81c30440594e4aa92d62812504086f82a95da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 16 Nov 2020 20:24:04 +0100 Subject: [PATCH 023/394] Reword taiko easy mod description to fit others better Co-authored-by: Joseph Madamba --- osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index 0ec24412e9..d1ad4c9d8d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModEasy : ModEasy { - public override string Description => @"Beats move slower, less accuracy required!"; + public override string Description => @"Beats move slower, and less accuracy required!"; } } From c6618f08aad3b47cc69ef73153966a5f384b580c Mon Sep 17 00:00:00 2001 From: kamp Date: Mon, 16 Nov 2020 21:26:08 +0100 Subject: [PATCH 024/394] Fix slider control point connections not being updated --- .../PathControlPointConnectionPiece.cs | 12 +++++++++- .../Components/PathControlPointVisualiser.cs | 24 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index ba1d35c35c..45c4a61ce1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -20,7 +20,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private readonly Path path; private readonly Slider slider; - private readonly int controlPointIndex; + private int controlPointIndex; + + public int ControlPointIndex + { + get => controlPointIndex; + set + { + controlPointIndex = value; + updateConnectingPath(); + } + } private IBindable sliderPosition; private IBindable pathVersion; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 17541866ec..14ce0e065b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -66,6 +66,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components switch (e.Action) { case NotifyCollectionChangedAction.Add: + // If inserting in the the path (not appending), + // update indices of existing connections after insert location + if (e.NewStartingIndex < Pieces.Count) + { + foreach (var connection in Connections) + { + if (connection.ControlPointIndex >= e.NewStartingIndex) + connection.ControlPointIndex += e.NewItems.Count; + } + } + for (int i = 0; i < e.NewItems.Count; i++) { var point = (PathControlPoint)e.NewItems[i]; @@ -82,12 +93,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components break; case NotifyCollectionChangedAction.Remove: + int oldSize = Pieces.Count; + foreach (var point in e.OldItems.Cast()) { Pieces.RemoveAll(p => p.ControlPoint == point); Connections.RemoveAll(c => c.ControlPoint == point); } + // If removing before the end of the path, + // update indices of connections after remove location + if (e.OldStartingIndex + e.OldItems.Count < oldSize) + { + foreach (var connection in Connections) + { + if (connection.ControlPointIndex >= e.OldStartingIndex) + connection.ControlPointIndex -= e.OldItems.Count; + } + } + break; } } From 33c643e36907d0c5830b1a13118c58d0b816ff11 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 12:23:34 +0900 Subject: [PATCH 025/394] Add obsoletion for unused property --- osu.Game/Graphics/Backgrounds/Triangles.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 5b0fa44444..a81dbbb64a 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -60,6 +60,7 @@ namespace osu.Game.Graphics.Backgrounds /// /// Whether we want to expire triangles as they exit our draw area completely. /// + [Obsolete("Unused.")] // Can be removed 20210518 protected virtual bool ExpireOffScreenTriangles => true; /// From 3bcf9c255a483a9936671b5c4acadc0e7b475d5a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 13:06:30 +0900 Subject: [PATCH 026/394] Add Triangles.Reset() --- osu.Game/Graphics/Backgrounds/Triangles.cs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index a81dbbb64a..0e9382279a 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -87,12 +87,9 @@ namespace osu.Game.Graphics.Backgrounds /// public float Velocity = 1; - private readonly Random stableRandom; - - private float nextRandom() => (float)(stableRandom?.NextDouble() ?? RNG.NextSingle()); - private readonly SortedList parts = new SortedList(Comparer.Default); + private Random stableRandom; private IShader shader; private readonly Texture texture; @@ -173,7 +170,20 @@ namespace osu.Game.Graphics.Backgrounds } } - protected int AimCount; + /// + /// Clears and re-initialises triangles according to a given seed. + /// + /// An optional seed to stabilise random positions / attributes. Note that this does not guarantee stable playback when seeking in time. + public void Reset(int? seed = null) + { + if (seed != null) + stableRandom = new Random(seed.Value); + + parts.Clear(); + addTriangles(true); + } + + protected int AimCount { get; private set; } private void addTriangles(bool randomY) { @@ -227,6 +237,8 @@ namespace osu.Game.Graphics.Backgrounds } } + private float nextRandom() => (float)(stableRandom?.NextDouble() ?? RNG.NextSingle()); + protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(this); private class TrianglesDrawNode : DrawNode From c101f32db8a796f08f21d317ffb5aef8310f8d14 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 13:06:52 +0900 Subject: [PATCH 027/394] Reset osu! triangle pieces on hitobject application --- .../Objects/Drawables/Pieces/CirclePiece.cs | 28 ++++++++++++++-- .../Objects/Drawables/Pieces/ExplodePiece.cs | 32 ++++++++++++++++++- .../Drawables/Pieces/TrianglesPiece.cs | 1 + 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index c455c66e8d..d0e1055dce 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -13,6 +13,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class CirclePiece : CompositeDrawable { + [Resolved] + private DrawableHitObject drawableObject { get; set; } + + private TrianglesPiece triangles; + public CirclePiece() { Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -26,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } [BackgroundDependencyLoader] - private void load(TextureStore textures, DrawableHitObject drawableHitObject) + private void load(TextureStore textures) { InternalChildren = new Drawable[] { @@ -36,13 +41,32 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Origin = Anchor.Centre, Texture = textures.Get(@"Gameplay/osu/disc"), }, - new TrianglesPiece(drawableHitObject.GetHashCode()) + triangles = new TrianglesPiece { RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, Alpha = 0.5f, } }; + + drawableObject.HitObjectApplied += onHitObjectApplied; + onHitObjectApplied(drawableObject); + } + + private void onHitObjectApplied(DrawableHitObject obj) + { + if (obj.HitObject == null) + return; + + triangles.Reset((int)obj.HitObject.StartTime); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (drawableObject != null) + drawableObject.HitObjectApplied -= onHitObjectApplied; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs index 6381ddca69..09299a3622 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs @@ -1,14 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class ExplodePiece : Container { + [Resolved] + private DrawableHitObject drawableObject { get; set; } + + private TrianglesPiece triangles; + public ExplodePiece() { Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -18,13 +25,36 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Blending = BlendingParameters.Additive; Alpha = 0; + } - Child = new TrianglesPiece + [BackgroundDependencyLoader] + private void load() + { + Child = triangles = new TrianglesPiece { Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, Alpha = 0.2f, }; + + drawableObject.HitObjectApplied += onHitObjectApplied; + onHitObjectApplied(drawableObject); + } + + private void onHitObjectApplied(DrawableHitObject obj) + { + if (obj.HitObject == null) + return; + + triangles.Reset((int)obj.HitObject.StartTime); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (drawableObject != null) + drawableObject.HitObjectApplied -= onHitObjectApplied; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs index add62192f0..53dc7ecea3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs @@ -7,6 +7,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class TrianglesPiece : Triangles { + protected override bool CreateNewTriangles => false; protected override float SpawnRatio => 0.5f; public TrianglesPiece(int? seed = null) From 77942af3a653aa3f8672d44f5370e56236c03df6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 13:37:58 +0900 Subject: [PATCH 028/394] Fix hold note judgements displaying incorrectly --- .../Objects/Drawables/DrawableHoldNoteTick.cs | 2 -- osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- osu.Game.Rulesets.Mania/UI/Stage.cs | 4 ++++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index f265419aa0..98931dceed 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -16,8 +16,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableHoldNoteTick : DrawableManiaHitObject { - public override bool DisplayResult => false; - /// /// References the time at which the user started holding the hold note. /// diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index c28a1c13d8..9aabcc6699 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI if (result.IsHit) hitPolicy.HandleHit(judgedObject); - if (!result.IsHit || !DisplayJudgements.Value) + if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result))); diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index e7a2de266d..3d7960ffe3 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -167,6 +167,10 @@ namespace osu.Game.Rulesets.Mania.UI if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; + // Tick judgements should not display text. + if (judgedObject is DrawableHoldNoteTick) + return; + judgements.Clear(false); judgements.Add(judgementPool.Get(j => { From 9dfa4249e059f68a54677625fb05227a401de745 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 14:05:13 +0900 Subject: [PATCH 029/394] Make Apply non-virtual --- .../Objects/Drawables/DrawableOsuJudgement.cs | 17 ++++++----------- .../Rulesets/Judgements/DrawableJudgement.cs | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 98898ce1b4..5bf5f89b26 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -39,23 +39,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }); } - public override void Apply(JudgementResult result, DrawableHitObject judgedObject) - { - base.Apply(result, judgedObject); - - if (judgedObject?.HitObject is OsuHitObject osuObject) - { - Position = osuObject.StackedPosition; - Scale = new Vector2(osuObject.Scale); - } - } - protected override void PrepareForUse() { base.PrepareForUse(); Lighting.ResetAnimation(); Lighting.SetColourFrom(JudgedObject, Result); + + if (JudgedObject?.HitObject is OsuHitObject osuObject) + { + Position = osuObject.StackedPosition; + Scale = new Vector2(osuObject.Scale); + } } private double fadeOutDelay; diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index d24c81536e..5c617aaa98 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Judgements this.Delay(FadeOutDelay).FadeOut(400); } - public virtual void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) + public void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) { Result = result; JudgedObject = judgedObject; From 82c3925a3795345d74d95f48fa4ebd2dd6e23f4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 14:13:50 +0900 Subject: [PATCH 030/394] Remove unused DrawableOsuJudgement constructors --- .../Objects/Drawables/DrawableOsuJudgement.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 5bf5f89b26..d89a613e0f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -17,15 +17,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [Resolved] private OsuConfigManager config { get; set; } - public DrawableOsuJudgement(JudgementResult result, DrawableHitObject judgedObject) - : base(result, judgedObject) - { - } - - public DrawableOsuJudgement() - { - } - [BackgroundDependencyLoader] private void load() { From f465dd5a5e6f840169c15129ce9698441c0bd82b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 14:59:34 +0900 Subject: [PATCH 031/394] Move all extended animation logic out of DrawableJudgement --- .../UI/DrawableManiaJudgement.cs | 21 +++--- .../Objects/Drawables/DrawableOsuJudgement.cs | 18 ++++- .../Judgements/DefaultJudgementPiece.cs | 65 +++++++++++++++++ .../Rulesets/Judgements/DrawableJudgement.cs | 69 ++++++++++--------- .../Judgements/IAnimatableJudgement.cs | 15 ++++ 5 files changed, 146 insertions(+), 42 deletions(-) create mode 100644 osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs create mode 100644 osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index d99f6cb8d3..c53ab4a717 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.UI { @@ -19,13 +19,6 @@ namespace osu.Game.Rulesets.Mania.UI { } - [BackgroundDependencyLoader] - private void load() - { - if (JudgementText != null) - JudgementText.Font = JudgementText.Font.With(size: 25); - } - protected override double FadeInDuration => 50; protected override void ApplyHitAnimations() @@ -36,5 +29,17 @@ namespace osu.Game.Rulesets.Mania.UI JudgementBody.Delay(FadeInDuration).ScaleTo(0.75f, 250); this.Delay(FadeInDuration).FadeOut(200); } + + protected override Drawable CreateDefaultJudgement(HitResult type) + => new ManiaJudgementPiece(); + + private class ManiaJudgementPiece : DefaultJudgementPiece + { + protected override void LoadComplete() + { + base.LoadComplete(); + JudgementText.Font = JudgementText.Font.With(size: 25); + } + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index d89a613e0f..a96ec53e28 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -4,9 +4,9 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; -using osuTK; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -65,8 +65,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables fadeOutDelay = hitLightingEnabled ? 1400 : base.FadeOutDelay; - JudgementText?.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); base.ApplyHitAnimations(); } + + protected override Drawable CreateDefaultJudgement(HitResult type) => new OsuJudgementPiece(); + + private class OsuJudgementPiece : DefaultJudgementPiece + { + public override void PlayAnimation(HitResult resultType) + { + base.PlayAnimation(resultType); + + if (resultType != HitResult.Miss) + JudgementText.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); + } + } } } diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs new file mode 100644 index 0000000000..051cd755d6 --- /dev/null +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; +using osuTK; + +namespace osu.Game.Rulesets.Judgements +{ + public class DefaultJudgementPiece : CompositeDrawable, IAnimatableJudgement + { + protected SpriteText JudgementText { get; } + + [Resolved] + private OsuColour colours { get; set; } + + public DefaultJudgementPiece() + { + Origin = Anchor.Centre; + + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + JudgementText = new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 20), + Scale = new Vector2(0.85f, 1), + } + }; + } + + public virtual void PlayAnimation(HitResult result) + { + JudgementText.Text = result.GetDescription().ToUpperInvariant(); + JudgementText.Colour = colours.ForHitResult(result); + + this.RotateTo(0); + this.MoveTo(Vector2.Zero); + + switch (result) + { + case HitResult.Miss: + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); + + this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + + this.RotateTo(40, 800, Easing.InQuint); + break; + + default: + this.ScaleTo(0.9f); + this.ScaleTo(1, 500, Easing.OutElastic); + break; + } + } + } +} diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 5c617aaa98..a73b422ccf 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -3,18 +3,14 @@ using System.Diagnostics; using JetBrains.Annotations; -using osuTK; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Judgements { @@ -25,16 +21,13 @@ namespace osu.Game.Rulesets.Judgements { private const float judgement_size = 128; - [Resolved] - private OsuColour colours { get; set; } - public JudgementResult Result { get; private set; } + public DrawableHitObject JudgedObject { get; private set; } protected Container JudgementBody { get; private set; } - protected SpriteText JudgementText { get; private set; } - private SkinnableDrawable bodyDrawable; + private SkinnableDrawable skinnableJudgement; /// /// Duration of initial fade in. @@ -69,14 +62,34 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); } + /// + /// Apply top-level animations to the current judgement when successfully hit. + /// Generally used for fading, defaulting to a simple fade out based on . + /// This will be used to calculate the lifetime of the judgement. + /// + /// + /// For animating the actual "default skin" judgement itself, it is recommended to use . + /// This allows applying animations which don't affect custom skins. + /// protected virtual void ApplyHitAnimations() { - JudgementBody.ScaleTo(0.9f); - JudgementBody.ScaleTo(1, 500, Easing.OutElastic); - this.Delay(FadeOutDelay).FadeOut(400); } + /// + /// Apply top-level animations to the current judgement when missed. + /// Generally used for fading, defaulting to a simple fade out based on . + /// This will be used to calculate the lifetime of the judgement. + /// + /// + /// For animating the actual "default skin" judgement itself, it is recommended to use . + /// This allows applying animations which don't affect custom skins. + /// + protected virtual void ApplyMissAnimations() + { + this.Delay(600).FadeOut(200); + } + public void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) { Result = result; @@ -91,12 +104,9 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); - bodyDrawable.ResetAnimation(); + skinnableJudgement.ResetAnimation(); this.FadeInFromZero(FadeInDuration, Easing.OutQuint); - JudgementBody.ScaleTo(1); - JudgementBody.RotateTo(0); - JudgementBody.MoveTo(Vector2.Zero); switch (Result.Type) { @@ -104,13 +114,7 @@ namespace osu.Game.Rulesets.Judgements break; case HitResult.Miss: - JudgementBody.ScaleTo(1.6f); - JudgementBody.ScaleTo(1, 100, Easing.In); - - JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); - JudgementBody.RotateTo(40, 800, Easing.InQuint); - - this.Delay(600).FadeOut(200); + ApplyMissAnimations(); break; default: @@ -118,6 +122,12 @@ namespace osu.Game.Rulesets.Judgements break; } + if (skinnableJudgement.Drawable is IAnimatableJudgement animatable) + { + using (BeginAbsoluteSequence(Result.TimeAbsolute)) + animatable.PlayAnimation(Result.Type); + } + Expire(true); } @@ -139,16 +149,13 @@ namespace osu.Game.Rulesets.Judgements Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Child = bodyDrawable = new SkinnableDrawable(new GameplaySkinComponent(type), _ => JudgementText = new OsuSpriteText - { - Text = type.GetDescription().ToUpperInvariant(), - Font = OsuFont.Numeric.With(size: 20), - Colour = colours.ForHitResult(type), - Scale = new Vector2(0.85f, 1), - }, confineMode: ConfineMode.NoScaling) + Child = skinnableJudgement = new SkinnableDrawable(new GameplaySkinComponent(type), _ => + CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling) }); currentDrawableType = type; } + + protected virtual Drawable CreateDefaultJudgement(HitResult type) => new DefaultJudgementPiece(); } } diff --git a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs new file mode 100644 index 0000000000..3f84e6f83c --- /dev/null +++ b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Judgements +{ + /// + /// A skinnable judgement element which supports playing an animation from the current point in time. + /// + public interface IAnimatableJudgement + { + void PlayAnimation(HitResult result); + } +} From e4f1e52422931ec11939a497b1db2e425be1ebce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 15:03:26 +0900 Subject: [PATCH 032/394] Add xmldoc coverage of Apply() --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index a73b422ccf..724b4a4d4e 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -90,6 +90,11 @@ namespace osu.Game.Rulesets.Judgements this.Delay(600).FadeOut(200); } + /// + /// Associate a new result / object with this judgement. Should be called when retrieving a judgement from a pool. + /// + /// The applicable judgement. + /// The drawable object. public void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) { Result = result; @@ -104,6 +109,7 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); + // not sure if this should remain going forward. skinnableJudgement.ResetAnimation(); this.FadeInFromZero(FadeInDuration, Easing.OutQuint); From 8247e6ce917053c1e881ef6ce27ce2fcdf3fc456 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 15:43:54 +0900 Subject: [PATCH 033/394] Move result type to ctor --- .../UI/DrawableManiaJudgement.cs | 9 +++++++-- .../Objects/Drawables/DrawableOsuJudgement.cs | 13 ++++++++---- .../Judgements/DefaultJudgementPiece.cs | 20 ++++++++++++------- .../Rulesets/Judgements/DrawableJudgement.cs | 5 +++-- .../Judgements/IAnimatableJudgement.cs | 4 +--- 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index c53ab4a717..ebce40a785 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -30,14 +30,19 @@ namespace osu.Game.Rulesets.Mania.UI this.Delay(FadeInDuration).FadeOut(200); } - protected override Drawable CreateDefaultJudgement(HitResult type) - => new ManiaJudgementPiece(); + protected override Drawable CreateDefaultJudgement(HitResult result) => new ManiaJudgementPiece(result); private class ManiaJudgementPiece : DefaultJudgementPiece { + public ManiaJudgementPiece(HitResult result) + : base(result) + { + } + protected override void LoadComplete() { base.LoadComplete(); + JudgementText.Font = JudgementText.Font.With(size: 25); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index a96ec53e28..47fb53379f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -68,15 +68,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.ApplyHitAnimations(); } - protected override Drawable CreateDefaultJudgement(HitResult type) => new OsuJudgementPiece(); + protected override Drawable CreateDefaultJudgement(HitResult result) => new OsuJudgementPiece(result); private class OsuJudgementPiece : DefaultJudgementPiece { - public override void PlayAnimation(HitResult resultType) + public OsuJudgementPiece(HitResult result) + : base(result) { - base.PlayAnimation(resultType); + } - if (resultType != HitResult.Miss) + public override void PlayAnimation() + { + base.PlayAnimation(); + + if (Result != HitResult.Miss) JudgementText.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); } } diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 051cd755d6..3b9e5e948a 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -15,36 +15,42 @@ namespace osu.Game.Rulesets.Judgements { public class DefaultJudgementPiece : CompositeDrawable, IAnimatableJudgement { - protected SpriteText JudgementText { get; } + protected readonly HitResult Result; + + protected SpriteText JudgementText { get; private set; } [Resolved] private OsuColour colours { get; set; } - public DefaultJudgementPiece() + public DefaultJudgementPiece(HitResult result) { + this.Result = result; Origin = Anchor.Centre; + } + [BackgroundDependencyLoader] + private void load() + { AutoSizeAxes = Axes.Both; InternalChildren = new Drawable[] { JudgementText = new OsuSpriteText { + Text = Result.GetDescription().ToUpperInvariant(), + Colour = colours.ForHitResult(Result), Font = OsuFont.Numeric.With(size: 20), Scale = new Vector2(0.85f, 1), } }; } - public virtual void PlayAnimation(HitResult result) + public virtual void PlayAnimation() { - JudgementText.Text = result.GetDescription().ToUpperInvariant(); - JudgementText.Colour = colours.ForHitResult(result); - this.RotateTo(0); this.MoveTo(Vector2.Zero); - switch (result) + switch (Result) { case HitResult.Miss: this.ScaleTo(1.6f); diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 724b4a4d4e..468b3190b0 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Judgements if (skinnableJudgement.Drawable is IAnimatableJudgement animatable) { using (BeginAbsoluteSequence(Result.TimeAbsolute)) - animatable.PlayAnimation(Result.Type); + animatable.PlayAnimation(); } Expire(true); @@ -143,6 +143,7 @@ namespace osu.Game.Rulesets.Judgements { var type = Result?.Type ?? HitResult.Perfect; //TODO: better default type from ruleset + // todo: this should be removed once judgements are always pooled. if (type == currentDrawableType) return; @@ -162,6 +163,6 @@ namespace osu.Game.Rulesets.Judgements currentDrawableType = type; } - protected virtual Drawable CreateDefaultJudgement(HitResult type) => new DefaultJudgementPiece(); + protected virtual Drawable CreateDefaultJudgement(HitResult result) => new DefaultJudgementPiece(result); } } diff --git a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs index 3f84e6f83c..3d5bbe6dad 100644 --- a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Rulesets.Scoring; - namespace osu.Game.Rulesets.Judgements { /// @@ -10,6 +8,6 @@ namespace osu.Game.Rulesets.Judgements /// public interface IAnimatableJudgement { - void PlayAnimation(HitResult result); + void PlayAnimation(); } } From dd4b69feab226c09c7334f407431add26828832f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 15:44:15 +0900 Subject: [PATCH 034/394] Add legacy judgement implementation which doesn't transform on animations --- osu.Game/Skinning/LegacyJudgementPiece.cs | 58 +++++++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 37 +++++++++------ 2 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Skinning/LegacyJudgementPiece.cs diff --git a/osu.Game/Skinning/LegacyJudgementPiece.cs b/osu.Game/Skinning/LegacyJudgementPiece.cs new file mode 100644 index 0000000000..6c606c638d --- /dev/null +++ b/osu.Game/Skinning/LegacyJudgementPiece.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osuTK; + +namespace osu.Game.Skinning +{ + public class LegacyJudgementPiece : CompositeDrawable, IAnimatableJudgement + { + private readonly HitResult result; + + public LegacyJudgementPiece(HitResult result, Drawable texture) + { + this.result = result; + + AutoSizeAxes = Axes.Both; + Origin = Anchor.Centre; + + InternalChild = texture; + } + + public virtual void PlayAnimation() + { + var animation = InternalChild as IFramedAnimation; + + animation?.GotoFrame(0); + + this.RotateTo(0); + this.MoveTo(Vector2.Zero); + + // legacy judgements don't play any transforms if they are an animation. + if (animation?.FrameCount > 1) + return; + + switch (result) + { + case HitResult.Miss: + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); + + this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + + this.RotateTo(40, 800, Easing.InQuint); + break; + + default: + this.ScaleTo(0.9f); + this.ScaleTo(1, 500, Easing.OutElastic); + break; + } + } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index fb020f4e39..ca8bb58023 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -371,20 +371,9 @@ namespace osu.Game.Skinning } case GameplaySkinComponent resultComponent: - switch (resultComponent.Component) - { - case HitResult.Miss: - return this.GetAnimation("hit0", true, false); - - case HitResult.Meh: - return this.GetAnimation("hit50", true, false); - - case HitResult.Ok: - return this.GetAnimation("hit100", true, false); - - case HitResult.Great: - return this.GetAnimation("hit300", true, false); - } + var drawable = getJudgementAnimation(resultComponent.Component); + if (drawable != null) + return new LegacyJudgementPiece(resultComponent.Component, drawable); break; } @@ -392,6 +381,26 @@ namespace osu.Game.Skinning return this.GetAnimation(component.LookupName, false, false); } + private Drawable getJudgementAnimation(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return this.GetAnimation("hit0", true, false); + + case HitResult.Meh: + return this.GetAnimation("hit50", true, false); + + case HitResult.Ok: + return this.GetAnimation("hit100", true, false); + + case HitResult.Great: + return this.GetAnimation("hit300", true, false); + } + + return null; + } + public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { foreach (var name in getFallbackNames(componentName)) From eebce1f9145813b525400517e170d3931a1f8de9 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 17 Nov 2020 18:13:32 +0900 Subject: [PATCH 035/394] Fix TestSceneFruitObjects --- .../TestSceneFruitObjects.cs | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index 385d8ed7fa..e9dabd30ed 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Rulesets.Catch.Tests @@ -36,17 +37,29 @@ namespace osu.Game.Rulesets.Catch.Tests Scale = 1.5f, }; - return new DrawableTinyDroplet(droplet) + return new TestDrawableTinyDroplet(droplet) { Anchor = Anchor.Centre, RelativePositionAxes = Axes.None, Position = Vector2.Zero, - Alpha = 1, - LifetimeStart = double.NegativeInfinity, - LifetimeEnd = double.PositiveInfinity, }; } + private class TestDrawableTinyDroplet : DrawableTinyDroplet + { + public TestDrawableTinyDroplet(TinyDroplet tinyDroplet) + : base(tinyDroplet) + { + } + + protected override void OnApply(HitObject hitObject) + { + base.OnApply(hitObject); + LifetimeStart = double.NegativeInfinity; + LifetimeEnd = double.PositiveInfinity; + } + } + private Drawable createDrawableDroplet(bool hyperdash = false) { var droplet = new TestCatchDroplet @@ -55,17 +68,29 @@ namespace osu.Game.Rulesets.Catch.Tests HyperDashTarget = hyperdash ? new Banana() : null }; - return new DrawableDroplet(droplet) + return new TestDrawableDroplet(droplet) { Anchor = Anchor.Centre, RelativePositionAxes = Axes.None, Position = Vector2.Zero, - Alpha = 1, - LifetimeStart = double.NegativeInfinity, - LifetimeEnd = double.PositiveInfinity, }; } + private class TestDrawableDroplet : DrawableDroplet + { + public TestDrawableDroplet(Droplet droplet) + : base(droplet) + { + } + + protected override void OnApply(HitObject hitObject) + { + base.OnApply(hitObject); + LifetimeStart = double.NegativeInfinity; + LifetimeEnd = double.PositiveInfinity; + } + } + private Drawable createDrawable(FruitVisualRepresentation rep, bool hyperdash = false) { Fruit fruit = new TestCatchFruit(rep) @@ -74,17 +99,29 @@ namespace osu.Game.Rulesets.Catch.Tests HyperDashTarget = hyperdash ? new Banana() : null }; - return new DrawableFruit(fruit) + return new TestDrawableFruit(fruit) { Anchor = Anchor.Centre, RelativePositionAxes = Axes.None, Position = Vector2.Zero, - Alpha = 1, - LifetimeStart = double.NegativeInfinity, - LifetimeEnd = double.PositiveInfinity, }; } + private class TestDrawableFruit : DrawableFruit + { + public TestDrawableFruit(Fruit fruit) + : base(fruit) + { + } + + protected override void OnApply(HitObject hitObject) + { + base.OnApply(hitObject); + LifetimeStart = double.NegativeInfinity; + LifetimeEnd = double.PositiveInfinity; + } + } + public class TestCatchFruit : Fruit { public TestCatchFruit(FruitVisualRepresentation rep) From 58c8184ad7f7b008d001a048cfcbf998f14e1423 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 22:56:21 +0900 Subject: [PATCH 036/394] Define blueprint order similarly to hitobjects --- .../Compose/Components/BlueprintContainer.cs | 6 +- .../Components/SelectionBlueprintContainer.cs | 76 +++++++++++++++++++ .../Timeline/TimelineBlueprintContainer.cs | 6 +- 3 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 53b6e14940..3aaa0c7d89 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -118,8 +118,8 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - protected virtual Container CreateSelectionBlueprintContainer() => - new Container { RelativeSizeAxes = Axes.Both }; + protected virtual SelectionBlueprintContainer CreateSelectionBlueprintContainer() => + new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; /// /// Creates a which outlines s and handles movement of selections. @@ -338,7 +338,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether a selection was performed. private bool beginClickSelection(MouseButtonEvent e) { - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren) + foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse()) { if (!blueprint.IsHovered) continue; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs new file mode 100644 index 0000000000..54932f6252 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs @@ -0,0 +1,76 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Edit; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class SelectionBlueprintContainer : Container + { + public override void Add(SelectionBlueprint drawable) + { + base.Add(drawable); + + if (Content == this) + bindStartTime(drawable); + } + + public override bool Remove(SelectionBlueprint drawable) + { + if (!base.Remove(drawable)) + return false; + + if (Content == this) + unbindStartTime(drawable); + return true; + } + + public override void Clear(bool disposeChildren) + { + base.Clear(disposeChildren); + unbindAllStartTimes(); + } + + private readonly Dictionary startTimeMap = new Dictionary(); + + private void bindStartTime(SelectionBlueprint blueprint) + { + var bindable = blueprint.HitObject.StartTimeBindable.GetBoundCopy(); + + bindable.BindValueChanged(_ => + { + if (LoadState >= LoadState.Ready) + SortInternal(); + }); + + startTimeMap[blueprint] = bindable; + } + + private void unbindStartTime(SelectionBlueprint blueprint) + { + startTimeMap[blueprint].UnbindAll(); + startTimeMap.Remove(blueprint); + } + + private void unbindAllStartTimes() + { + foreach (var kvp in startTimeMap) + kvp.Value.UnbindAll(); + startTimeMap.Clear(); + } + + protected override int Compare(Drawable x, Drawable y) + { + if (!(x is SelectionBlueprint xObj) || !(y is SelectionBlueprint yObj)) + return base.Compare(x, y); + + // Put earlier blueprints towards the end of the list, so they handle input first + int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime); + return i == 0 ? CompareReverseChildID(x, y) : i; + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index eef02e61a6..2bd4ac2f91 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected override Container CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected override SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; protected override void OnDrag(DragEvent e) { @@ -195,13 +195,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected class TimelineSelectionBlueprintContainer : Container + protected class TimelineSelectionBlueprintContainer : SelectionBlueprintContainer { protected override Container Content { get; } public TimelineSelectionBlueprintContainer() { - AddInternal(new TimelinePart(Content = new Container { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); + AddInternal(new TimelinePart(Content = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); } } } From 04805b78c31718a451737bd78b090e96e39a3859 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 23:19:59 +0900 Subject: [PATCH 037/394] Tighten osu! ruleset lifetime expiry for past hitobjects --- .../Objects/Drawables/DrawableHitCircle.cs | 2 ++ .../Objects/Drawables/DrawableOsuHitObject.cs | 5 +++++ .../Objects/Drawables/DrawableSlider.cs | 16 ++++++++-------- .../Objects/Drawables/DrawableSpinner.cs | 2 +- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 2e63160d36..d1ceca6d8f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -180,6 +180,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables this.Delay(800).FadeOut(); break; } + + Expire(); } public Drawable ProxiedLayer => ApproachCircle; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index bcaf73d34f..c962d191a6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -91,6 +92,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // Manually set to reduce the number of future alive objects to a bare minimum. LifetimeStart = HitObject.StartTime - HitObject.TimePreempt; + + // Arbitrary lifetime end to prevent past objects in idle states remaining alive in non-frame-stable contexts. + // An extra 1000ms is added to always overestimate the true lifetime, and a more exact value is set by hit transforms and the following expiry. + LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss) + 1000; } /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index f7b1894058..14c494d909 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -193,13 +193,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return base.CreateNestedHitObject(hitObject); } - protected override void UpdateInitialTransforms() - { - base.UpdateInitialTransforms(); - - Body.FadeInFromZero(HitObject.TimeFadeIn); - } - public readonly Bindable Tracking = new Bindable(); protected override void Update() @@ -273,6 +266,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.PlaySamples(); } + protected override void UpdateInitialTransforms() + { + base.UpdateInitialTransforms(); + + Body.FadeInFromZero(HitObject.TimeFadeIn); + } + protected override void UpdateStartTimeStateTransforms() { base.UpdateStartTimeStateTransforms(); @@ -297,7 +297,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; } - this.FadeOut(fade_out_time, Easing.OutQuint); + this.FadeOut(fade_out_time, Easing.OutQuint).Expire(); } public Drawable ProxiedLayer => HeadCircle.ProxiedLayer; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 87c7146a64..2a14a7c975 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateHitStateTransforms(state); - this.FadeOut(160); + this.FadeOut(160).Expire(); // skin change does a rewind of transforms, which will stop the spinning sound from playing if it's currently in playback. isSpinning?.TriggerChange(); From ce4baf328dba51f3f5006bbc69f0ad58b9d91dd6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 23:35:36 +0900 Subject: [PATCH 038/394] Move into OnApply() to resolve one-frame issues --- .../Objects/Drawables/DrawableOsuHitObject.cs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index c962d191a6..a26db06ede 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -61,6 +61,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables PositionBindable.BindTo(HitObject.PositionBindable); StackHeightBindable.BindTo(HitObject.StackHeightBindable); ScaleBindable.BindTo(HitObject.ScaleBindable); + + // Manually set to reduce the number of future alive objects to a bare minimum. + LifetimeStart = HitObject.StartTime - HitObject.TimePreempt; + + // Arbitrary lifetime end to prevent past objects in idle states remaining alive in non-frame-stable contexts. + // An extra 1000ms is added to always overestimate the true lifetime, and a more exact value is set by hit transforms and the following expiry. + LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss) + 1000; } protected override void OnFree(HitObject hitObject) @@ -86,18 +93,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength); - protected override void UpdateInitialTransforms() - { - base.UpdateInitialTransforms(); - - // Manually set to reduce the number of future alive objects to a bare minimum. - LifetimeStart = HitObject.StartTime - HitObject.TimePreempt; - - // Arbitrary lifetime end to prevent past objects in idle states remaining alive in non-frame-stable contexts. - // An extra 1000ms is added to always overestimate the true lifetime, and a more exact value is set by hit transforms and the following expiry. - LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss) + 1000; - } - /// /// Causes this to get missed, disregarding all conditions in implementations of . /// From c360533e4cce0875dbb5175ef6a27bac869759f1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 17 Nov 2020 23:40:30 +0900 Subject: [PATCH 039/394] Simplify code of TestSceneFruitObjects --- .../TestSceneFruitObjects.cs | 131 ++++-------------- 1 file changed, 24 insertions(+), 107 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index e9dabd30ed..89063319d6 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; -using osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Rulesets.Catch.Tests @@ -19,107 +18,42 @@ namespace osu.Game.Rulesets.Catch.Tests base.LoadComplete(); foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation))) - AddStep($"show {rep}", () => SetContents(() => createDrawable(rep))); + AddStep($"show {rep}", () => SetContents(() => createDrawableFruit(rep))); AddStep("show droplet", () => SetContents(() => createDrawableDroplet())); AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet)); foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation))) - AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawable(rep, true))); + AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawableFruit(rep, true))); AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true))); } - private Drawable createDrawableTinyDroplet() + private Drawable createDrawableFruit(FruitVisualRepresentation rep, bool hyperdash = false) => + setProperties(new DrawableFruit(new TestCatchFruit(rep)), hyperdash); + + private Drawable createDrawableDroplet(bool hyperdash = false) => setProperties(new DrawableDroplet(new Droplet()), hyperdash); + + private Drawable createDrawableTinyDroplet() => setProperties(new DrawableTinyDroplet(new TinyDroplet())); + + private DrawableCatchHitObject setProperties(DrawableCatchHitObject d, bool hyperdash = false) { - var droplet = new TestCatchTinyDroplet + var hitObject = d.HitObject; + hitObject.StartTime = 1000000000000; + hitObject.Scale = 1.5f; + + if (hyperdash) + hitObject.HyperDashTarget = new Banana(); + + d.Anchor = Anchor.Centre; + d.RelativePositionAxes = Axes.None; + d.Position = Vector2.Zero; + d.HitObjectApplied += _ => { - Scale = 1.5f, + d.LifetimeStart = double.NegativeInfinity; + d.LifetimeEnd = double.PositiveInfinity; }; - - return new TestDrawableTinyDroplet(droplet) - { - Anchor = Anchor.Centre, - RelativePositionAxes = Axes.None, - Position = Vector2.Zero, - }; - } - - private class TestDrawableTinyDroplet : DrawableTinyDroplet - { - public TestDrawableTinyDroplet(TinyDroplet tinyDroplet) - : base(tinyDroplet) - { - } - - protected override void OnApply(HitObject hitObject) - { - base.OnApply(hitObject); - LifetimeStart = double.NegativeInfinity; - LifetimeEnd = double.PositiveInfinity; - } - } - - private Drawable createDrawableDroplet(bool hyperdash = false) - { - var droplet = new TestCatchDroplet - { - Scale = 1.5f, - HyperDashTarget = hyperdash ? new Banana() : null - }; - - return new TestDrawableDroplet(droplet) - { - Anchor = Anchor.Centre, - RelativePositionAxes = Axes.None, - Position = Vector2.Zero, - }; - } - - private class TestDrawableDroplet : DrawableDroplet - { - public TestDrawableDroplet(Droplet droplet) - : base(droplet) - { - } - - protected override void OnApply(HitObject hitObject) - { - base.OnApply(hitObject); - LifetimeStart = double.NegativeInfinity; - LifetimeEnd = double.PositiveInfinity; - } - } - - private Drawable createDrawable(FruitVisualRepresentation rep, bool hyperdash = false) - { - Fruit fruit = new TestCatchFruit(rep) - { - Scale = 1.5f, - HyperDashTarget = hyperdash ? new Banana() : null - }; - - return new TestDrawableFruit(fruit) - { - Anchor = Anchor.Centre, - RelativePositionAxes = Axes.None, - Position = Vector2.Zero, - }; - } - - private class TestDrawableFruit : DrawableFruit - { - public TestDrawableFruit(Fruit fruit) - : base(fruit) - { - } - - protected override void OnApply(HitObject hitObject) - { - base.OnApply(hitObject); - LifetimeStart = double.NegativeInfinity; - LifetimeEnd = double.PositiveInfinity; - } + return d; } public class TestCatchFruit : Fruit @@ -127,26 +61,9 @@ namespace osu.Game.Rulesets.Catch.Tests public TestCatchFruit(FruitVisualRepresentation rep) { VisualRepresentation = rep; - StartTime = 1000000000000; } public override FruitVisualRepresentation VisualRepresentation { get; } } - - public class TestCatchDroplet : Droplet - { - public TestCatchDroplet() - { - StartTime = 1000000000000; - } - } - - public class TestCatchTinyDroplet : TinyDroplet - { - public TestCatchTinyDroplet() - { - StartTime = 1000000000000; - } - } } } From 875d7dec7999f96e03c9454ce256d283ff299286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Nov 2020 20:11:13 +0100 Subject: [PATCH 040/394] Remove redundant `this.` qualifier --- osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 3b9e5e948a..7fe3917893 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Judgements public DefaultJudgementPiece(HitResult result) { - this.Result = result; + Result = result; Origin = Anchor.Centre; } From 57eaee27aec1110f869c9ca5f25262bede0e0188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Nov 2020 20:12:04 +0100 Subject: [PATCH 041/394] Rename param to match type better --- osu.Game/Skinning/LegacyJudgementPiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPiece.cs b/osu.Game/Skinning/LegacyJudgementPiece.cs index 6c606c638d..3def37e22c 100644 --- a/osu.Game/Skinning/LegacyJudgementPiece.cs +++ b/osu.Game/Skinning/LegacyJudgementPiece.cs @@ -14,14 +14,14 @@ namespace osu.Game.Skinning { private readonly HitResult result; - public LegacyJudgementPiece(HitResult result, Drawable texture) + public LegacyJudgementPiece(HitResult result, Drawable drawable) { this.result = result; AutoSizeAxes = Axes.Both; Origin = Anchor.Centre; - InternalChild = texture; + InternalChild = drawable; } public virtual void PlayAnimation() From c8fb49d540d4876fb3b7800c4159da6f5239fe24 Mon Sep 17 00:00:00 2001 From: kamp Date: Tue, 17 Nov 2020 22:23:46 +0100 Subject: [PATCH 042/394] Apply suggestions and remove redundant updateConnectingPath call --- .../PathControlPointConnectionPiece.cs | 16 +++------------- .../Components/PathControlPointVisualiser.cs | 6 ++---- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index 45c4a61ce1..eb7011e8b0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -20,17 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private readonly Path path; private readonly Slider slider; - private int controlPointIndex; - - public int ControlPointIndex - { - get => controlPointIndex; - set - { - controlPointIndex = value; - updateConnectingPath(); - } - } + public int ControlPointIndex { get; set; } private IBindable sliderPosition; private IBindable pathVersion; @@ -38,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public PathControlPointConnectionPiece(Slider slider, int controlPointIndex) { this.slider = slider; - this.controlPointIndex = controlPointIndex; + ControlPointIndex = controlPointIndex; Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; @@ -74,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components path.ClearVertices(); - int nextIndex = controlPointIndex + 1; + int nextIndex = ControlPointIndex + 1; if (nextIndex == 0 || nextIndex >= slider.Path.ControlPoints.Count) return; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 14ce0e065b..e551be4af3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components switch (e.Action) { case NotifyCollectionChangedAction.Add: - // If inserting in the the path (not appending), + // If inserting in the path (not appending), // update indices of existing connections after insert location if (e.NewStartingIndex < Pieces.Count) { @@ -93,8 +93,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components break; case NotifyCollectionChangedAction.Remove: - int oldSize = Pieces.Count; - foreach (var point in e.OldItems.Cast()) { Pieces.RemoveAll(p => p.ControlPoint == point); @@ -103,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // If removing before the end of the path, // update indices of connections after remove location - if (e.OldStartingIndex + e.OldItems.Count < oldSize) + if (e.OldStartingIndex + e.OldItems.Count < Pieces.Count + e.OldItems.Count) { foreach (var connection in Connections) { From 2d66423fbdc37cba19f675897297a8efa2131784 Mon Sep 17 00:00:00 2001 From: kamp Date: Tue, 17 Nov 2020 23:04:38 +0100 Subject: [PATCH 043/394] Simplify inequality --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index e551be4af3..7375c0e981 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // If removing before the end of the path, // update indices of connections after remove location - if (e.OldStartingIndex + e.OldItems.Count < Pieces.Count + e.OldItems.Count) + if (e.OldStartingIndex < Pieces.Count) { foreach (var connection in Connections) { From 783c172b5de26e53b1ff0982f8f49a9bcd7bbad1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 18 Nov 2020 13:33:22 +0900 Subject: [PATCH 044/394] Make sealed and cleanup comparator --- .../Edit/Compose/Components/BlueprintContainer.cs | 3 +-- ...ner.cs => HitObjectOrderedSelectionContainer.cs} | 13 +++++-------- .../Timeline/TimelineBlueprintContainer.cs | 6 +++--- 3 files changed, 9 insertions(+), 13 deletions(-) rename osu.Game/Screens/Edit/Compose/Components/{SelectionBlueprintContainer.cs => HitObjectOrderedSelectionContainer.cs} (85%) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 3aaa0c7d89..4b98d42c7c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -118,8 +118,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - protected virtual SelectionBlueprintContainer CreateSelectionBlueprintContainer() => - new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected virtual Container CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }; /// /// Creates a which outlines s and handles movement of selections. diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs similarity index 85% rename from osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs rename to osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index 54932f6252..ae50b0fd61 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -9,14 +9,12 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Screens.Edit.Compose.Components { - public class SelectionBlueprintContainer : Container + public sealed class HitObjectOrderedSelectionContainer : Container { public override void Add(SelectionBlueprint drawable) { base.Add(drawable); - - if (Content == this) - bindStartTime(drawable); + bindStartTime(drawable); } public override bool Remove(SelectionBlueprint drawable) @@ -24,8 +22,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (!base.Remove(drawable)) return false; - if (Content == this) - unbindStartTime(drawable); + unbindStartTime(drawable); return true; } @@ -65,8 +62,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override int Compare(Drawable x, Drawable y) { - if (!(x is SelectionBlueprint xObj) || !(y is SelectionBlueprint yObj)) - return base.Compare(x, y); + var xObj = (SelectionBlueprint)x; + var yObj = (SelectionBlueprint)y; // Put earlier blueprints towards the end of the list, so they handle input first int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 2bd4ac2f91..2f14c607c2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected override SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected override Container CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; protected override void OnDrag(DragEvent e) { @@ -195,13 +195,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected class TimelineSelectionBlueprintContainer : SelectionBlueprintContainer + protected class TimelineSelectionBlueprintContainer : Container { protected override Container Content { get; } public TimelineSelectionBlueprintContainer() { - AddInternal(new TimelinePart(Content = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); + AddInternal(new TimelinePart(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); } } } From f00c23b4a07367179d67c7291e1305f1ca01ab3d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 18 Nov 2020 13:37:15 +0900 Subject: [PATCH 045/394] Add comment + xmldoc --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 1 + .../Compose/Components/HitObjectOrderedSelectionContainer.cs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 4b98d42c7c..df9cadebfc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -337,6 +337,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether a selection was performed. private bool beginClickSelection(MouseButtonEvent e) { + // Iterate from the top of the input stack (blueprints closest to the front of the screen first). foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse()) { if (!blueprint.IsHovered) continue; diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index ae50b0fd61..9e95fe4fa1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -6,9 +6,13 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit.Compose.Components { + /// + /// A container for ordered by their start times. + /// public sealed class HitObjectOrderedSelectionContainer : Container { public override void Add(SelectionBlueprint drawable) From efc18887c8182d6414ff2b8efe47c1b4f709525e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 15:51:09 +0900 Subject: [PATCH 046/394] Update framework --- osu.Android.props | 2 +- osu.Desktop/OsuGameDesktop.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 6e3d5eec1f..4657896fac 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index b17611f23f..0feab9a717 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -139,7 +139,7 @@ namespace osu.Desktop // SDL2 DesktopWindow case DesktopWindow desktopWindow: - desktopWindow.CursorState.Value |= CursorState.Hidden; + desktopWindow.CursorState |= CursorState.Hidden; desktopWindow.SetIconFromStream(iconStream); desktopWindow.Title = Name; desktopWindow.DragDrop += f => fileDrop(new[] { f }); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1850ee3488..704ac5a611 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 2ac23f1503..346bd892b0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - + From bb1aacb360758c2c5e21e172bcb9f1985933a2b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 16:18:27 +0900 Subject: [PATCH 047/394] Fix SkinnableSprite initialising a drawable even when the texture is not available --- osu.Game/Skinning/SkinnableSprite.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 5352928ec6..1340d1474c 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -24,7 +24,15 @@ namespace osu.Game.Skinning { } - protected override Drawable CreateDefault(ISkinComponent component) => new Sprite { Texture = textures.Get(component.LookupName) }; + protected override Drawable CreateDefault(ISkinComponent component) + { + var texture = textures.Get(component.LookupName); + + if (texture == null) + return null; + + return new Sprite { Texture = texture }; + } private class SpriteComponent : ISkinComponent { From 8be31f4805385708909b4405dd3c2aeb427bda15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 23:55:21 +0900 Subject: [PATCH 048/394] Adjust legacy skin judgement transforms to match stable --- osu.Game/Skinning/LegacyJudgementPiece.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPiece.cs b/osu.Game/Skinning/LegacyJudgementPiece.cs index 3def37e22c..44aa5106e2 100644 --- a/osu.Game/Skinning/LegacyJudgementPiece.cs +++ b/osu.Game/Skinning/LegacyJudgementPiece.cs @@ -14,6 +14,8 @@ namespace osu.Game.Skinning { private readonly HitResult result; + private bool hasParticle; + public LegacyJudgementPiece(HitResult result, Drawable drawable) { this.result = result; @@ -37,6 +39,8 @@ namespace osu.Game.Skinning if (animation?.FrameCount > 1) return; + const double animation_length = 500; + switch (result) { case HitResult.Miss: @@ -49,8 +53,19 @@ namespace osu.Game.Skinning break; default: - this.ScaleTo(0.9f); - this.ScaleTo(1, 500, Easing.OutElastic); + if (!hasParticle) + { + this.ScaleTo(0.6f).Then() + .ScaleTo(1.1f, animation_length * 0.8f).Then() + .ScaleTo(0.9f, animation_length * 0.4f).Then() + .ScaleTo(1f, animation_length * 0.2f); + } + else + { + this.ScaleTo(0.9f); + this.ScaleTo(1.05f, animation_length); + } + break; } } From 5bd4ace37f73cffd0550812e455bb7c3335cf623 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 15:38:26 +0900 Subject: [PATCH 049/394] Split out new and old style legacy judgement pieces --- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 93 +++++++++++++++++++ ...entPiece.cs => LegacyJudgementPieceOld.cs} | 31 +++---- osu.Game/Skinning/LegacySkin.cs | 30 +++++- 3 files changed, 133 insertions(+), 21 deletions(-) create mode 100644 osu.Game/Skinning/LegacyJudgementPieceNew.cs rename osu.Game/Skinning/{LegacyJudgementPiece.cs => LegacyJudgementPieceOld.cs} (61%) diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs new file mode 100644 index 0000000000..de77df9e10 --- /dev/null +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osuTK; + +namespace osu.Game.Skinning +{ + public class LegacyJudgementPieceNew : CompositeDrawable, IAnimatableJudgement + { + private readonly HitResult result; + + private readonly LegacyJudgementPieceOld temporaryOldStyle; + + private readonly Drawable mainPiece; + + public LegacyJudgementPieceNew(HitResult result, Func createMainDrawable, Drawable particleDrawable) + { + this.result = result; + + AutoSizeAxes = Axes.Both; + Origin = Anchor.Centre; + + InternalChildren = new[] + { + mainPiece = createMainDrawable().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + }) + }; + + if (result != HitResult.Miss) + { + //new judgement shows old as a temporary effect + AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f) + { + Blending = BlendingParameters.Additive, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + } + + public void PlayAnimation() + { + var animation = mainPiece as IFramedAnimation; + + animation?.GotoFrame(0); + + this.RotateTo(0); + this.MoveTo(Vector2.Zero); + + if (temporaryOldStyle != null) + { + temporaryOldStyle.PlayAnimation(); + + temporaryOldStyle.Hide(); + temporaryOldStyle.Delay(-16) + .FadeTo(0.5f, 56, Easing.Out).Then() + .FadeOut(300); + } + + // legacy judgements don't play any transforms if they are an animation. + if (animation?.FrameCount > 1) + return; + + switch (result) + { + case HitResult.Miss: + mainPiece.ScaleTo(1.6f); + mainPiece.ScaleTo(1, 100, Easing.In); + + mainPiece.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + + mainPiece.RotateTo(40, 800, Easing.InQuint); + break; + + default: + const double animation_length = 1100; + + mainPiece.ScaleTo(0.9f); + mainPiece.ScaleTo(1.05f, animation_length); + break; + } + } + } +} diff --git a/osu.Game/Skinning/LegacyJudgementPiece.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs similarity index 61% rename from osu.Game/Skinning/LegacyJudgementPiece.cs rename to osu.Game/Skinning/LegacyJudgementPieceOld.cs index 44aa5106e2..63d2c44dd9 100644 --- a/osu.Game/Skinning/LegacyJudgementPiece.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.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.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; @@ -10,20 +11,21 @@ using osuTK; namespace osu.Game.Skinning { - public class LegacyJudgementPiece : CompositeDrawable, IAnimatableJudgement + public class LegacyJudgementPieceOld : CompositeDrawable, IAnimatableJudgement { private readonly HitResult result; - private bool hasParticle; + private readonly float finalScale; - public LegacyJudgementPiece(HitResult result, Drawable drawable) + public LegacyJudgementPieceOld(HitResult result, Func createMainDrawable, float finalScale = 1f) { this.result = result; + this.finalScale = finalScale; AutoSizeAxes = Axes.Both; Origin = Anchor.Centre; - InternalChild = drawable; + InternalChild = createMainDrawable(); } public virtual void PlayAnimation() @@ -39,8 +41,6 @@ namespace osu.Game.Skinning if (animation?.FrameCount > 1) return; - const double animation_length = 500; - switch (result) { case HitResult.Miss: @@ -53,19 +53,14 @@ namespace osu.Game.Skinning break; default: - if (!hasParticle) - { - this.ScaleTo(0.6f).Then() - .ScaleTo(1.1f, animation_length * 0.8f).Then() - .ScaleTo(0.9f, animation_length * 0.4f).Then() - .ScaleTo(1f, animation_length * 0.2f); - } - else - { - this.ScaleTo(0.9f); - this.ScaleTo(1.05f, animation_length); - } + const double animation_length = 120; + this.ScaleTo(0.6f).Then() + .ScaleTo(1.1f, animation_length * 0.8f).Then() + // this is actually correct to match stable; there were overlapping transforms. + .ScaleTo(0.9f).Delay(animation_length * 0.2f) + .ScaleTo(1.1f).ScaleTo(0.9f, animation_length * 0.2f).Then() + .ScaleTo(0.95f).ScaleTo(finalScale, animation_length * 0.2f); break; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index ca8bb58023..ce1a736f0a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -371,9 +371,16 @@ namespace osu.Game.Skinning } case GameplaySkinComponent resultComponent: - var drawable = getJudgementAnimation(resultComponent.Component); - if (drawable != null) - return new LegacyJudgementPiece(resultComponent.Component, drawable); + Func createDrawable = () => getJudgementAnimation(resultComponent.Component); + + if (createDrawable() != null) + { + var particles = getParticleTexture(resultComponent.Component); + if (particles != null) + return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, getParticleTexture(resultComponent.Component)); + else + return new LegacyJudgementPieceOld(resultComponent.Component, createDrawable); + } break; } @@ -381,6 +388,23 @@ namespace osu.Game.Skinning return this.GetAnimation(component.LookupName, false, false); } + private Drawable getParticleTexture(HitResult result) + { + switch (result) + { + case HitResult.Meh: + return this.GetAnimation("particle50", false, false); + + case HitResult.Ok: + return this.GetAnimation("particle100", false, false); + + case HitResult.Great: + return this.GetAnimation("particle300", false, false); + } + + return null; + } + private Drawable getJudgementAnimation(HitResult result) { switch (result) From 94886a09b23b2c7d850781eaa20c07b0697a2d3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 15:39:02 +0900 Subject: [PATCH 050/394] Remove fades from DrawableJudgement itself --- .../UI/DrawableManiaJudgement.cs | 7 +++---- .../Objects/Drawables/DrawableOsuJudgement.cs | 14 +++----------- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 11 +++++++---- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index ebce40a785..a341cdd8ec 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -19,15 +19,14 @@ namespace osu.Game.Rulesets.Mania.UI { } - protected override double FadeInDuration => 50; - protected override void ApplyHitAnimations() { JudgementBody.ScaleTo(0.8f); JudgementBody.ScaleTo(1, 250, Easing.OutElastic); - JudgementBody.Delay(FadeInDuration).ScaleTo(0.75f, 250); - this.Delay(FadeInDuration).FadeOut(200); + JudgementBody.Delay(50) + .ScaleTo(0.75f, 250) + .FadeOut(200); } protected override Drawable CreateDefaultJudgement(HitResult result) => new ManiaJudgementPiece(result); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 47fb53379f..90133fb01c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -44,26 +44,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - private double fadeOutDelay; - protected override double FadeOutDelay => fadeOutDelay; - protected override void ApplyHitAnimations() { bool hitLightingEnabled = config.Get(OsuSetting.HitLighting); if (hitLightingEnabled) { - JudgementBody.FadeIn().Delay(FadeInDuration).FadeOut(400); - Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out); Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000); - } - else - { - JudgementBody.Alpha = 1; - } - fadeOutDelay = hitLightingEnabled ? 1400 : base.FadeOutDelay; + // extend the lifetime to cover lighting fade + LifetimeEnd = 1400; + } base.ApplyHitAnimations(); } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 468b3190b0..09b8353c63 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -32,11 +33,13 @@ namespace osu.Game.Rulesets.Judgements /// /// Duration of initial fade in. /// + [Obsolete("Apply any animations manually via ApplyHitAnimations / ApplyMissAnimations. Defaults were moved inside skinned components.")] protected virtual double FadeInDuration => 100; /// /// Duration to wait until fade out begins. Defaults to . /// + [Obsolete("Apply any animations manually via ApplyHitAnimations / ApplyMissAnimations. Defaults were moved inside skinned components.")] protected virtual double FadeOutDelay => FadeInDuration; /// @@ -73,7 +76,6 @@ namespace osu.Game.Rulesets.Judgements /// protected virtual void ApplyHitAnimations() { - this.Delay(FadeOutDelay).FadeOut(400); } /// @@ -112,8 +114,6 @@ namespace osu.Game.Rulesets.Judgements // not sure if this should remain going forward. skinnableJudgement.ResetAnimation(); - this.FadeInFromZero(FadeInDuration, Easing.OutQuint); - switch (Result.Type) { case HitResult.None: @@ -134,7 +134,10 @@ namespace osu.Game.Rulesets.Judgements animatable.PlayAnimation(); } - Expire(true); + JudgementBody.Expire(true); + + LifetimeStart = JudgementBody.LifetimeStart; + LifetimeEnd = JudgementBody.LifetimeEnd; } private HitResult? currentDrawableType; From 25d4511e49d6c7db38eb8e6ab349bbfd2434a56b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 16:09:51 +0900 Subject: [PATCH 051/394] Fix judgement test scene always using hitobjects at t=0 --- osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 646f12f710..7ea2ef3a78 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -89,7 +89,13 @@ namespace osu.Game.Rulesets.Osu.Tests Children = new Drawable[] { pool, - pool.Get(j => j.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)).With(j => + pool.Get(j => j.Apply(new JudgementResult(new HitObject + { + StartTime = Time.Current + }, new Judgement()) + { + Type = result, + }, null)).With(j => { j.Anchor = Anchor.Centre; j.Origin = Anchor.Centre; From 72a15ef2dc51f877dd1416128a43af38f2b718fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 15:51:09 +0900 Subject: [PATCH 052/394] Handle DrawableJudgement lifetime more flexibly --- .../Objects/Drawables/DrawableOsuJudgement.cs | 2 +- .../Rulesets/Judgements/DrawableJudgement.cs | 51 +++++++++++-------- .../Judgements/IAnimatableJudgement.cs | 4 +- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 90133fb01c..b06c55102c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000); // extend the lifetime to cover lighting fade - LifetimeEnd = 1400; + LifetimeEnd = Lighting.LatestTransformEndTime; } base.ApplyHitAnimations(); diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 09b8353c63..45b3e229f3 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -89,7 +89,6 @@ namespace osu.Game.Rulesets.Judgements /// protected virtual void ApplyMissAnimations() { - this.Delay(600).FadeOut(200); } /// @@ -111,33 +110,43 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); - // not sure if this should remain going forward. - skinnableJudgement.ResetAnimation(); + LifetimeStart = Result.TimeAbsolute; - switch (Result.Type) + using (BeginAbsoluteSequence(Result.TimeAbsolute, true)) { - case HitResult.None: - break; + // not sure if this should remain going forward. + skinnableJudgement.ResetAnimation(); - case HitResult.Miss: - ApplyMissAnimations(); - break; + switch (Result.Type) + { + case HitResult.None: + break; - default: - ApplyHitAnimations(); - break; - } + case HitResult.Miss: + ApplyMissAnimations(); + break; + + default: + ApplyHitAnimations(); + break; + } + + if (skinnableJudgement.Drawable is IAnimatableJudgement animatable) + { + var drawableAnimation = (Drawable)animatable; + + drawableAnimation.ClearTransforms(); - if (skinnableJudgement.Drawable is IAnimatableJudgement animatable) - { - using (BeginAbsoluteSequence(Result.TimeAbsolute)) animatable.PlayAnimation(); + + drawableAnimation.Expire(true); + + // a derived version of DrawableJudgement may be adjusting lifetime. + // if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime. + if (LifetimeEnd == double.MaxValue || drawableAnimation.LifetimeEnd > LifetimeEnd) + LifetimeEnd = drawableAnimation.LifetimeEnd; + } } - - JudgementBody.Expire(true); - - LifetimeStart = JudgementBody.LifetimeStart; - LifetimeEnd = JudgementBody.LifetimeEnd; } private HitResult? currentDrawableType; diff --git a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs index 3d5bbe6dad..32312f1115 100644 --- a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs @@ -1,12 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; + namespace osu.Game.Rulesets.Judgements { /// /// A skinnable judgement element which supports playing an animation from the current point in time. /// - public interface IAnimatableJudgement + public interface IAnimatableJudgement : IDrawable { void PlayAnimation(); } From 9d0a6de26e6039cf87dfd8c64f1f0ffb7f3643d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 16:18:27 +0900 Subject: [PATCH 053/394] Fix SkinnableSprite initialising a drawable even when the texture is not available --- osu.Game/Skinning/SkinnableSprite.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 5352928ec6..1340d1474c 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -24,7 +24,15 @@ namespace osu.Game.Skinning { } - protected override Drawable CreateDefault(ISkinComponent component) => new Sprite { Texture = textures.Get(component.LookupName) }; + protected override Drawable CreateDefault(ISkinComponent component) + { + var texture = textures.Get(component.LookupName); + + if (texture == null) + return null; + + return new Sprite { Texture = texture }; + } private class SpriteComponent : ISkinComponent { From 9d3de5bca0441ea4b04cab337c9ccdc074b170ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 16:18:58 +0900 Subject: [PATCH 054/394] Fix hit lighting dictating lifetime even when not present in skin --- .../Objects/Drawables/DrawableOsuJudgement.cs | 2 +- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index b06c55102c..c81cfd97dd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { bool hitLightingEnabled = config.Get(OsuSetting.HitLighting); - if (hitLightingEnabled) + if (hitLightingEnabled && Lighting.Drawable != null) { Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out); Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000); diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 45b3e229f3..c0fcb1eb3c 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -139,12 +139,14 @@ namespace osu.Game.Rulesets.Judgements animatable.PlayAnimation(); - drawableAnimation.Expire(true); - // a derived version of DrawableJudgement may be adjusting lifetime. // if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime. - if (LifetimeEnd == double.MaxValue || drawableAnimation.LifetimeEnd > LifetimeEnd) - LifetimeEnd = drawableAnimation.LifetimeEnd; + double lastTransformTime = drawableAnimation.LatestTransformEndTime; + + if (LifetimeEnd == double.MaxValue || lastTransformTime > LifetimeEnd) + { + LifetimeEnd = lastTransformTime; + } } } } From c47e70da9b4c6ac4ac337221b747f09ce3f5ab9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 17:15:45 +0900 Subject: [PATCH 055/394] Update animations for new/old miss style and add fades --- .../Judgements/DefaultJudgementPiece.cs | 8 ++++-- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 28 +++++++++++++------ osu.Game/Skinning/LegacyJudgementPieceOld.cs | 25 ++++++++++------- osu.Game/Skinning/LegacySkin.cs | 8 +++--- 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 7fe3917893..b89c1f4e4f 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -47,18 +47,18 @@ namespace osu.Game.Rulesets.Judgements public virtual void PlayAnimation() { - this.RotateTo(0); - this.MoveTo(Vector2.Zero); - switch (Result) { case HitResult.Miss: this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); + this.MoveTo(Vector2.Zero); this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + this.RotateTo(0); this.RotateTo(40, 800, Easing.InQuint); + break; default: @@ -66,6 +66,8 @@ namespace osu.Game.Rulesets.Judgements this.ScaleTo(1, 500, Easing.OutElastic); break; } + + this.FadeOutFromOne(800); } } } diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index de77df9e10..d0a2f1d4eb 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osuTK; @@ -53,9 +54,14 @@ namespace osu.Game.Skinning animation?.GotoFrame(0); - this.RotateTo(0); - this.MoveTo(Vector2.Zero); + const double fade_in_length = 120; + const double fade_out_delay = 500; + const double fade_out_length = 600; + this.FadeInFromZero(fade_in_length); + this.Delay(fade_out_delay).FadeOut(fade_out_length); + + // new style non-miss judgements show the original style temporarily, with additive colour. if (temporaryOldStyle != null) { temporaryOldStyle.PlayAnimation(); @@ -73,19 +79,23 @@ namespace osu.Game.Skinning switch (result) { case HitResult.Miss: - mainPiece.ScaleTo(1.6f); - mainPiece.ScaleTo(1, 100, Easing.In); + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); - mainPiece.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + //todo: this only applies to osu! ruleset apparently. + this.MoveTo(new Vector2(0, -2)); + this.MoveToOffset(new Vector2(0, 20), fade_out_delay + fade_out_length, Easing.In); - mainPiece.RotateTo(40, 800, Easing.InQuint); + float rotation = RNG.NextSingle(-8.6f, 8.6f); + + this.RotateTo(0); + this.RotateTo(rotation, fade_in_length) + .Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In); break; default: - const double animation_length = 1100; - mainPiece.ScaleTo(0.9f); - mainPiece.ScaleTo(1.05f, animation_length); + mainPiece.ScaleTo(1.05f, fade_out_delay + fade_out_length); break; } } diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 63d2c44dd9..3486dce081 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -5,9 +5,9 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osuTK; namespace osu.Game.Skinning { @@ -34,8 +34,12 @@ namespace osu.Game.Skinning animation?.GotoFrame(0); - this.RotateTo(0); - this.MoveTo(Vector2.Zero); + const double fade_in_length = 120; + const double fade_out_delay = 500; + const double fade_out_length = 600; + + this.FadeInFromZero(fade_in_length); + this.Delay(fade_out_delay).FadeOut(fade_out_length); // legacy judgements don't play any transforms if they are an animation. if (animation?.FrameCount > 1) @@ -47,20 +51,21 @@ namespace osu.Game.Skinning this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); - this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + float rotation = RNG.NextSingle(-8.6f, 8.6f); - this.RotateTo(40, 800, Easing.InQuint); + this.RotateTo(0); + this.RotateTo(rotation, fade_in_length) + .Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In); break; default: - const double animation_length = 120; this.ScaleTo(0.6f).Then() - .ScaleTo(1.1f, animation_length * 0.8f).Then() + .ScaleTo(1.1f, fade_in_length * 0.8f).Then() // this is actually correct to match stable; there were overlapping transforms. - .ScaleTo(0.9f).Delay(animation_length * 0.2f) - .ScaleTo(1.1f).ScaleTo(0.9f, animation_length * 0.2f).Then() - .ScaleTo(0.95f).ScaleTo(finalScale, animation_length * 0.2f); + .ScaleTo(0.9f).Delay(fade_in_length * 0.2f) + .ScaleTo(1.1f).ScaleTo(0.9f, fade_in_length * 0.2f).Then() + .ScaleTo(0.95f).ScaleTo(finalScale, fade_in_length * 0.2f); break; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index ce1a736f0a..9fbf3de043 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -373,11 +373,11 @@ namespace osu.Game.Skinning case GameplaySkinComponent resultComponent: Func createDrawable = () => getJudgementAnimation(resultComponent.Component); + // kind of wasteful that we throw this away, but should do for now. if (createDrawable() != null) { - var particles = getParticleTexture(resultComponent.Component); - if (particles != null) - return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, getParticleTexture(resultComponent.Component)); + if (Configuration.LegacyVersion > 1) + return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, getParticleDrawable(resultComponent.Component)); else return new LegacyJudgementPieceOld(resultComponent.Component, createDrawable); } @@ -388,7 +388,7 @@ namespace osu.Game.Skinning return this.GetAnimation(component.LookupName, false, false); } - private Drawable getParticleTexture(HitResult result) + private Drawable getParticleDrawable(HitResult result) { switch (result) { From d017e725fb87a1c3b22dfe17809defc002925db7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 17:15:53 +0900 Subject: [PATCH 056/394] Add comment for future todo task --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index c81cfd97dd..9699fb72a4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (hitLightingEnabled && Lighting.Drawable != null) { + // todo: this animation changes slightly based on new/old legacy skin versions. Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out); Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000); From ee8804b50b86e6d2bb85c18908f715fec0b599f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 17:53:27 +0900 Subject: [PATCH 057/394] Fix animation playback not running on skin change --- .../Rulesets/Judgements/DrawableJudgement.cs | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index c0fcb1eb3c..85875e2f2a 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -28,8 +28,13 @@ namespace osu.Game.Rulesets.Judgements protected Container JudgementBody { get; private set; } + public override bool RemoveCompletedTransforms => false; + private SkinnableDrawable skinnableJudgement; + [Resolved] + private ISkinSource skinSource { get; set; } + /// /// Duration of initial fade in. /// @@ -65,6 +70,27 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); } + protected override void LoadComplete() + { + base.LoadComplete(); + skinSource.SourceChanged += onSkinChanged; + } + + private void onSkinChanged() + { + // on a skin change, the child component will update but not get correctly triggered to play its animation. + // we need to trigger a reinitialisation to make things right. + currentDrawableType = null; + + PrepareForUse(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + skinSource.SourceChanged -= onSkinChanged; + } + /// /// Apply top-level animations to the current judgement when successfully hit. /// Generally used for fading, defaulting to a simple fade out based on . @@ -110,6 +136,12 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); + runAnimation(); + } + + private void runAnimation() + { + ClearTransforms(true); LifetimeStart = Result.TimeAbsolute; using (BeginAbsoluteSequence(Result.TimeAbsolute, true)) @@ -135,18 +167,13 @@ namespace osu.Game.Rulesets.Judgements { var drawableAnimation = (Drawable)animatable; - drawableAnimation.ClearTransforms(); - animatable.PlayAnimation(); - // a derived version of DrawableJudgement may be adjusting lifetime. + // a derived version of DrawableJudgement may be proposing a lifetime. // if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime. double lastTransformTime = drawableAnimation.LatestTransformEndTime; - if (LifetimeEnd == double.MaxValue || lastTransformTime > LifetimeEnd) - { LifetimeEnd = lastTransformTime; - } } } } From 1fd582d33341c396919d7f23920a18e55a00b982 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 19:12:29 +0900 Subject: [PATCH 058/394] Update lighting tests to not require lighting (some test skins are missing it) --- osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 7ea2ef3a78..b0119edc14 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -43,10 +43,8 @@ namespace osu.Game.Rulesets.Osu.Tests showResult(HitResult.Great); AddUntilStep("judgements shown", () => this.ChildrenOfType().Any()); - AddAssert("judgement body immediately visible", - () => this.ChildrenOfType().All(judgement => judgement.JudgementBody.Alpha == 1)); AddAssert("hit lighting hidden", - () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha == 0)); + () => this.ChildrenOfType().All(judgement => !judgement.Lighting.Transforms.Any())); } [Test] @@ -57,10 +55,8 @@ namespace osu.Game.Rulesets.Osu.Tests showResult(HitResult.Great); AddUntilStep("judgements shown", () => this.ChildrenOfType().Any()); - AddAssert("judgement body not immediately visible", - () => this.ChildrenOfType().All(judgement => judgement.JudgementBody.Alpha > 0 && judgement.JudgementBody.Alpha < 1)); AddAssert("hit lighting shown", - () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha > 0)); + () => this.ChildrenOfType().Any(judgement => judgement.Lighting.Transforms.Any())); } private void showResult(HitResult result) From 20bb64c627edd4043f6ff72886a2e91de506536c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 19:34:00 +0900 Subject: [PATCH 059/394] Fix mania misses not correctly animating (temporary solution) --- .../Skinning/TestSceneDrawableJudgement.cs | 5 +++- .../UI/DrawableManiaJudgement.cs | 28 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs index a4d4ec50f8..11536ac8c3 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs @@ -24,7 +24,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning if (hitWindows.IsHitResultAllowed(result)) { AddStep("Show " + result.GetDescription(), () => SetContents(() => - new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null) + new DrawableManiaJudgement(new JudgementResult(new HitObject + { + StartTime = Time.Current + }, new Judgement()) { Type = result }, null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index a341cdd8ec..a3dcd0e57f 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Mania.UI { @@ -19,6 +20,27 @@ namespace osu.Game.Rulesets.Mania.UI { } + protected override void ApplyMissAnimations() + { + if (!(JudgementBody.Drawable is DefaultManiaJudgementPiece)) + { + // this is temporary logic until mania's skin transformer returns IAnimatableJudgements + JudgementBody.ScaleTo(1.6f); + JudgementBody.ScaleTo(1, 100, Easing.In); + + JudgementBody.MoveTo(Vector2.Zero); + JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + + JudgementBody.RotateTo(0); + JudgementBody.RotateTo(40, 800, Easing.InQuint); + JudgementBody.FadeOutFromOne(800); + + LifetimeEnd = JudgementBody.LatestTransformEndTime; + } + + base.ApplyMissAnimations(); + } + protected override void ApplyHitAnimations() { JudgementBody.ScaleTo(0.8f); @@ -29,11 +51,11 @@ namespace osu.Game.Rulesets.Mania.UI .FadeOut(200); } - protected override Drawable CreateDefaultJudgement(HitResult result) => new ManiaJudgementPiece(result); + protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result); - private class ManiaJudgementPiece : DefaultJudgementPiece + private class DefaultManiaJudgementPiece : DefaultJudgementPiece { - public ManiaJudgementPiece(HitResult result) + public DefaultManiaJudgementPiece(HitResult result) : base(result) { } From 8522ddc61e4d9a795feb290a66f1f3be6a948dfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 19:34:27 +0900 Subject: [PATCH 060/394] Reduce nesting of skinned component to reduce exposed surface --- .../TestSceneDrawableJudgement.cs | 2 +- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index b0119edc14..1339d11ab6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests private class TestDrawableOsuJudgement : DrawableOsuJudgement { public new SkinnableSprite Lighting => base.Lighting; - public new Container JudgementBody => base.JudgementBody; + public new SkinnableDrawable JudgementBody => base.JudgementBody; } } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 85875e2f2a..72ffa3a34b 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -26,11 +25,9 @@ namespace osu.Game.Rulesets.Judgements public DrawableHitObject JudgedObject { get; private set; } - protected Container JudgementBody { get; private set; } - public override bool RemoveCompletedTransforms => false; - private SkinnableDrawable skinnableJudgement; + protected SkinnableDrawable JudgementBody { get; private set; } [Resolved] private ISkinSource skinSource { get; set; } @@ -147,7 +144,7 @@ namespace osu.Game.Rulesets.Judgements using (BeginAbsoluteSequence(Result.TimeAbsolute, true)) { // not sure if this should remain going forward. - skinnableJudgement.ResetAnimation(); + JudgementBody.ResetAnimation(); switch (Result.Type) { @@ -163,7 +160,7 @@ namespace osu.Game.Rulesets.Judgements break; } - if (skinnableJudgement.Drawable is IAnimatableJudgement animatable) + if (JudgementBody.Drawable is IAnimatableJudgement animatable) { var drawableAnimation = (Drawable)animatable; @@ -192,13 +189,11 @@ namespace osu.Game.Rulesets.Judgements if (JudgementBody != null) RemoveInternal(JudgementBody); - AddInternal(JudgementBody = new Container + AddInternal(JudgementBody = new SkinnableDrawable(new GameplaySkinComponent(type), _ => + CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Child = skinnableJudgement = new SkinnableDrawable(new GameplaySkinComponent(type), _ => - CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling) }); currentDrawableType = type; From b3bec81b79b5fa88edbce4ab7b322b10a9fb0db0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 19:38:38 +0900 Subject: [PATCH 061/394] Update xmldoc to match new behaviour --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 72ffa3a34b..6407cb40aa 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -90,8 +90,7 @@ namespace osu.Game.Rulesets.Judgements /// /// Apply top-level animations to the current judgement when successfully hit. - /// Generally used for fading, defaulting to a simple fade out based on . - /// This will be used to calculate the lifetime of the judgement. + /// If displaying components which require lifetime extensions, manually adjusting is required. /// /// /// For animating the actual "default skin" judgement itself, it is recommended to use . @@ -103,8 +102,7 @@ namespace osu.Game.Rulesets.Judgements /// /// Apply top-level animations to the current judgement when missed. - /// Generally used for fading, defaulting to a simple fade out based on . - /// This will be used to calculate the lifetime of the judgement. + /// If displaying components which require lifetime extensions, manually adjusting is required. /// /// /// For animating the actual "default skin" judgement itself, it is recommended to use . From 191b95810ce519e6b9b3990ae105867b113f753a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 19:47:35 +0900 Subject: [PATCH 062/394] Fix whitespace issues --- .../Skinning/TestSceneDrawableJudgement.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs index 11536ac8c3..dcb25f21ba 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs @@ -24,10 +24,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning if (hitWindows.IsHitResultAllowed(result)) { AddStep("Show " + result.GetDescription(), () => SetContents(() => - new DrawableManiaJudgement(new JudgementResult(new HitObject + new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement()) { - StartTime = Time.Current - }, new Judgement()) { Type = result }, null) + Type = result + }, null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From cb5d1d0d77f1dbf87a01d113074dcdb1f31e7b57 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 18 Nov 2020 21:26:35 +0900 Subject: [PATCH 063/394] Remove obsolete method --- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index d8c6da86f9..ba38c7f77d 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -10,14 +10,6 @@ namespace osu.Game.Rulesets.Objects { public static class SliderEventGenerator { - [Obsolete("Use the overload with cancellation support instead.")] // can be removed 20201115 - // ReSharper disable once RedundantOverload.Global - public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, - double? legacyLastTickOffset) - { - return Generate(startTime, spanDuration, velocity, tickDistance, totalDistance, spanCount, legacyLastTickOffset, default); - } - // ReSharper disable once MethodOverloadWithOptionalParameter public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, double? legacyLastTickOffset, CancellationToken cancellationToken = default) From 85c5c68dfac097bc15c9218853de000816b74f58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 13:20:21 +0900 Subject: [PATCH 064/394] Provide particle drawable as a function (for future use) --- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index d0a2f1d4eb..b5e1de337a 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -20,7 +20,7 @@ namespace osu.Game.Skinning private readonly Drawable mainPiece; - public LegacyJudgementPieceNew(HitResult result, Func createMainDrawable, Drawable particleDrawable) + public LegacyJudgementPieceNew(HitResult result, Func createMainDrawable, Func createParticleDrawable) { this.result = result; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 9fbf3de043..6faee8c2e7 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -377,7 +377,7 @@ namespace osu.Game.Skinning if (createDrawable() != null) { if (Configuration.LegacyVersion > 1) - return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, getParticleDrawable(resultComponent.Component)); + return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, () => getParticleDrawable(resultComponent.Component)); else return new LegacyJudgementPieceOld(resultComponent.Component, createDrawable); } From ba735584faac6f0c5ab081cbd1fed06b3b0ec7ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 14:04:43 +0900 Subject: [PATCH 065/394] Add null check for disposal safety --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 6407cb40aa..4aed38e689 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -85,7 +85,9 @@ namespace osu.Game.Rulesets.Judgements protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - skinSource.SourceChanged -= onSkinChanged; + + if (skinSource != null) + skinSource.SourceChanged -= onSkinChanged; } /// From 3024ae6d867b4eae0014aaba8d2ed7dbf0470bc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 14:10:07 +0900 Subject: [PATCH 066/394] Add better test coverage of hit lighting (and ensure reset after animation reapplication) --- osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs | 7 +++---- .../Objects/Drawables/DrawableOsuJudgement.cs | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 1339d11ab6..e4158d8f07 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -43,8 +43,8 @@ namespace osu.Game.Rulesets.Osu.Tests showResult(HitResult.Great); AddUntilStep("judgements shown", () => this.ChildrenOfType().Any()); - AddAssert("hit lighting hidden", - () => this.ChildrenOfType().All(judgement => !judgement.Lighting.Transforms.Any())); + AddAssert("hit lighting has no transforms", () => this.ChildrenOfType().All(judgement => !judgement.Lighting.Transforms.Any())); + AddAssert("hit lighting hidden", () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha == 0)); } [Test] @@ -55,8 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests showResult(HitResult.Great); AddUntilStep("judgements shown", () => this.ChildrenOfType().Any()); - AddAssert("hit lighting shown", - () => this.ChildrenOfType().Any(judgement => judgement.Lighting.Transforms.Any())); + AddUntilStep("hit lighting shown", () => this.ChildrenOfType().Any(judgement => judgement.Lighting.Alpha > 0)); } private void showResult(HitResult result) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 9699fb72a4..13f5960bd4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -48,6 +48,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { bool hitLightingEnabled = config.Get(OsuSetting.HitLighting); + Lighting.Alpha = 0; + if (hitLightingEnabled && Lighting.Drawable != null) { // todo: this animation changes slightly based on new/old legacy skin versions. From 9df93e1f186d4fa592b549ba6b1a15a4112847c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 14:54:31 +0900 Subject: [PATCH 067/394] Add basic implementation of particle explosion Using drawables still, just to get things in place and setup the structure --- .../Gameplay/TestSceneParticleExplosion.cs | 30 ++++++++++ osu.Game/Graphics/ParticleExplosion.cs | 59 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs create mode 100644 osu.Game/Graphics/ParticleExplosion.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs new file mode 100644 index 0000000000..6df4842608 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [TestFixture] + public class TestSceneParticleExplosion : OsuTestScene + { + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + AddStep(@"display", () => + { + Child = new ParticleExplosion(textures.Get("Cursor/cursortrail"), 150, 1200) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200) + }; + }); + } + } +} diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs new file mode 100644 index 0000000000..6a6f947dd5 --- /dev/null +++ b/osu.Game/Graphics/ParticleExplosion.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; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Utils; +using osuTK; + +namespace osu.Game.Graphics +{ + public class ParticleExplosion : CompositeDrawable + { + public ParticleExplosion(Texture texture, int particleCount, double duration) + { + for (int i = 0; i < particleCount; i++) + { + double rDuration = RNG.NextDouble(duration / 3, duration); + + AddInternal(new Particle(rDuration, RNG.NextSingle(0, MathF.PI * 2)) + { + Texture = texture + }); + } + } + + private class Particle : Sprite + { + private readonly double duration; + private readonly float direction; + + private Vector2 positionForOffset(float offset) => new Vector2( + (float)(offset * Math.Sin(direction)), + (float)(offset * Math.Cos(direction)) + ); + + public Particle(double duration, float direction) + { + this.duration = duration; + this.direction = direction; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativePositionAxes = Axes.Both; + Position = positionForOffset(0); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.MoveTo(positionForOffset(1), duration); + this.FadeOut(duration); + Expire(); + } + } + } +} From 9d04ce75ccba4d263a40e961552eed793a8348ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 15:47:02 +0900 Subject: [PATCH 068/394] Make particles additive and consume in judgement explosions --- osu.Game/Graphics/ParticleExplosion.cs | 24 ++++++++++++++++---- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 24 +++++++++++++++++++- osu.Game/Skinning/LegacySkin.cs | 10 ++++---- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index 6a6f947dd5..4daae34d62 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -26,11 +27,19 @@ namespace osu.Game.Graphics } } + public void Restart() + { + foreach (var p in InternalChildren.OfType()) + p.Play(); + } + private class Particle : Sprite { private readonly double duration; private readonly float direction; + public override bool RemoveWhenNotAlive => false; + private Vector2 positionForOffset(float offset) => new Vector2( (float)(offset * Math.Sin(direction)), (float)(offset * Math.Cos(direction)) @@ -40,18 +49,25 @@ namespace osu.Game.Graphics { this.duration = duration; this.direction = direction; - Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Blending = BlendingParameters.Additive; + RelativePositionAxes = Axes.Both; - Position = positionForOffset(0); } protected override void LoadComplete() { base.LoadComplete(); + Play(); + } - this.MoveTo(positionForOffset(1), duration); - this.FadeOut(duration); + public void Play() + { + this.MoveTo(new Vector2(0.5f)); + this.MoveTo(new Vector2(0.5f) + positionForOffset(0.5f), duration); + + this.FadeOutFromOne(duration); Expire(); } } diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index b5e1de337a..2a53820872 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -5,7 +5,9 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Textures; using osu.Framework.Utils; +using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osuTK; @@ -20,7 +22,9 @@ namespace osu.Game.Skinning private readonly Drawable mainPiece; - public LegacyJudgementPieceNew(HitResult result, Func createMainDrawable, Func createParticleDrawable) + private readonly ParticleExplosion particles; + + public LegacyJudgementPieceNew(HitResult result, Func createMainDrawable, Texture particleTexture) { this.result = result; @@ -36,6 +40,17 @@ namespace osu.Game.Skinning }) }; + if (particleTexture != null) + { + AddInternal(particles = new ParticleExplosion(particleTexture, 150, 1600) + { + Size = new Vector2(140), + Depth = float.MaxValue, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + if (result != HitResult.Miss) { //new judgement shows old as a temporary effect @@ -54,6 +69,13 @@ namespace osu.Game.Skinning animation?.GotoFrame(0); + if (particles != null) + { + // start the particles already some way into their animation to break cluster away from centre. + using (particles.BeginDelayedSequence(-100, true)) + particles.Restart(); + } + const double fade_in_length = 120; const double fade_out_delay = 500; const double fade_out_length = 600; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 6faee8c2e7..63a22eba62 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -377,7 +377,7 @@ namespace osu.Game.Skinning if (createDrawable() != null) { if (Configuration.LegacyVersion > 1) - return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, () => getParticleDrawable(resultComponent.Component)); + return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, getParticleTexture(resultComponent.Component)); else return new LegacyJudgementPieceOld(resultComponent.Component, createDrawable); } @@ -388,18 +388,18 @@ namespace osu.Game.Skinning return this.GetAnimation(component.LookupName, false, false); } - private Drawable getParticleDrawable(HitResult result) + private Texture getParticleTexture(HitResult result) { switch (result) { case HitResult.Meh: - return this.GetAnimation("particle50", false, false); + return GetTexture("particle50"); case HitResult.Ok: - return this.GetAnimation("particle100", false, false); + return GetTexture("particle100"); case HitResult.Great: - return this.GetAnimation("particle300", false, false); + return GetTexture("particle300"); } return null; From efd5acb8abb0e73a9e5ba632d592d485dc1f2443 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 15:55:11 +0900 Subject: [PATCH 069/394] Randomise direction every animation playback --- osu.Game/Graphics/ParticleExplosion.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index 4daae34d62..200bcc062a 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -20,7 +20,7 @@ namespace osu.Game.Graphics { double rDuration = RNG.NextDouble(duration / 3, duration); - AddInternal(new Particle(rDuration, RNG.NextSingle(0, MathF.PI * 2)) + AddInternal(new Particle(rDuration) { Texture = texture }); @@ -36,19 +36,12 @@ namespace osu.Game.Graphics private class Particle : Sprite { private readonly double duration; - private readonly float direction; public override bool RemoveWhenNotAlive => false; - private Vector2 positionForOffset(float offset) => new Vector2( - (float)(offset * Math.Sin(direction)), - (float)(offset * Math.Cos(direction)) - ); - - public Particle(double duration, float direction) + public Particle(double duration) { this.duration = duration; - this.direction = direction; Origin = Anchor.Centre; Blending = BlendingParameters.Additive; @@ -64,11 +57,18 @@ namespace osu.Game.Graphics public void Play() { + double direction = RNG.NextSingle(0, MathF.PI * 2); + this.MoveTo(new Vector2(0.5f)); this.MoveTo(new Vector2(0.5f) + positionForOffset(0.5f), duration); this.FadeOutFromOne(duration); Expire(); + + Vector2 positionForOffset(float offset) => new Vector2( + (float)(offset * Math.Sin(direction)), + (float)(offset * Math.Cos(direction)) + ); } } } From 83024f1ec524db962d88645c1e6fe5be9623a594 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 16:00:20 +0900 Subject: [PATCH 070/394] Add back positional randomness from stable --- osu.Game/Graphics/ParticleExplosion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index 200bcc062a..8a3be513be 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -60,7 +60,7 @@ namespace osu.Game.Graphics double direction = RNG.NextSingle(0, MathF.PI * 2); this.MoveTo(new Vector2(0.5f)); - this.MoveTo(new Vector2(0.5f) + positionForOffset(0.5f), duration); + this.MoveTo(new Vector2(0.5f) + positionForOffset(RNG.NextSingle(0.5f)), duration); this.FadeOutFromOne(duration); Expire(); From fe025043bd59d8164710cce507c16589ad439c98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 17:16:29 +0900 Subject: [PATCH 071/394] Make test run multiple times --- .../Visual/Gameplay/TestSceneParticleExplosion.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs index 6df4842608..63c8757afd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs @@ -16,15 +16,15 @@ namespace osu.Game.Tests.Visual.Gameplay [BackgroundDependencyLoader] private void load(TextureStore textures) { - AddStep(@"display", () => + AddRepeatStep(@"display", () => { Child = new ParticleExplosion(textures.Get("Cursor/cursortrail"), 150, 1200) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(200) + Size = new Vector2(400) }; - }); + }, 10); } } } From 476d0256ccc0d7620d84316fc7f3bb2657e4b2d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 17:22:37 +0900 Subject: [PATCH 072/394] Replace particle explosion implementation with DrawNode version --- osu.Game/Graphics/ParticleExplosion.cs | 128 ++++++++++++++++++------- 1 file changed, 96 insertions(+), 32 deletions(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index 8a3be513be..ba6d26ec22 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -2,9 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; +using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; @@ -12,64 +13,127 @@ using osuTK; namespace osu.Game.Graphics { - public class ParticleExplosion : CompositeDrawable + public class ParticleExplosion : Sprite { + private readonly double duration; + private double startTime; + + private readonly List parts = new List(); + public ParticleExplosion(Texture texture, int particleCount, double duration) { - for (int i = 0; i < particleCount; i++) - { - double rDuration = RNG.NextDouble(duration / 3, duration); + Texture = texture; + this.duration = duration; + Blending = BlendingParameters.Additive; - AddInternal(new Particle(rDuration) - { - Texture = texture - }); - } + for (int i = 0; i < particleCount; i++) + parts.Add(new ParticlePart(duration)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Restart(); } public void Restart() { - foreach (var p in InternalChildren.OfType()) - p.Play(); + startTime = TransformStartTime; + + this.FadeOutFromOne(duration); + + foreach (var p in parts) + p.Randomise(); } - private class Particle : Sprite + protected override void Update() { - private readonly double duration; + base.Update(); - public override bool RemoveWhenNotAlive => false; + Invalidate(Invalidation.DrawNode); + } - public Particle(double duration) + protected override DrawNode CreateDrawNode() => new ParticleExplosionDrawNode(this); + + private class ParticleExplosionDrawNode : SpriteDrawNode + { + private List parts = new List(); + + private ParticleExplosion source => (ParticleExplosion)Source; + + private double startTime; + private double currentTime; + private Vector2 sourceSize; + + public ParticleExplosionDrawNode(Sprite source) + : base(source) { - this.duration = duration; - - Origin = Anchor.Centre; - Blending = BlendingParameters.Additive; - - RelativePositionAxes = Axes.Both; } - protected override void LoadComplete() + public override void ApplyState() { - base.LoadComplete(); - Play(); + base.ApplyState(); + + parts = source.parts; + sourceSize = source.Size; + startTime = source.startTime; + currentTime = source.Time.Current; } - public void Play() + protected override void Blit(Action vertexAction) { - double direction = RNG.NextSingle(0, MathF.PI * 2); + foreach (var p in parts) + { + var pos = p.PositionAtTime(currentTime - startTime); - this.MoveTo(new Vector2(0.5f)); - this.MoveTo(new Vector2(0.5f) + positionForOffset(RNG.NextSingle(0.5f)), duration); + // todo: implement per particle. + var rect = new RectangleF(pos.X * sourceSize.X, pos.Y * sourceSize.Y, Texture.DisplayWidth, Texture.DisplayHeight); - this.FadeOutFromOne(duration); - Expire(); + var quad = new Quad( + Vector2Extensions.Transform(rect.TopLeft, DrawInfo.Matrix), + Vector2Extensions.Transform(rect.TopRight, DrawInfo.Matrix), + Vector2Extensions.Transform(rect.BottomLeft, DrawInfo.Matrix), + Vector2Extensions.Transform(rect.BottomRight, DrawInfo.Matrix) + ); + + DrawQuad(Texture, quad, DrawColourInfo.Colour, null, vertexAction, + new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height), + null, TextureCoords); + } + } + } + + private class ParticlePart + { + private readonly double totalDuration; + + private double duration; + private double direction; + private float distance; + + public ParticlePart(double totalDuration) + { + this.totalDuration = totalDuration; + + Randomise(); + } + + public Vector2 PositionAtTime(double time) + { + return new Vector2(0.5f) + positionForOffset(distance * (float)(time / duration)); Vector2 positionForOffset(float offset) => new Vector2( (float)(offset * Math.Sin(direction)), (float)(offset * Math.Cos(direction)) ); } + + public void Randomise() + { + distance = RNG.NextSingle(0.5f); + duration = RNG.NextDouble(totalDuration / 3, totalDuration); + direction = RNG.NextSingle(0, MathF.PI * 2); + } } } } From 3a7291c5cf699bfc4e83653a44ced17aa86a3f53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 17:56:11 +0900 Subject: [PATCH 073/394] Fix some behavioural regressions --- osu.Game/Graphics/ParticleExplosion.cs | 42 +++++++++++++++++--------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index ba6d26ec22..68dedf70a0 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -39,7 +39,6 @@ namespace osu.Game.Graphics public void Restart() { startTime = TransformStartTime; - this.FadeOutFromOne(duration); foreach (var p in parts) @@ -74,6 +73,8 @@ namespace osu.Game.Graphics { base.ApplyState(); + // this is mostly safe as the parts are immutable. + // the most that can go wrong is the random state be incorrect parts = source.parts; sourceSize = source.Size; startTime = source.startTime; @@ -82,13 +83,20 @@ namespace osu.Game.Graphics protected override void Blit(Action vertexAction) { + var time = currentTime - startTime; + foreach (var p in parts) { - var pos = p.PositionAtTime(currentTime - startTime); + Vector2 pos = p.PositionAtTime(time); + float alpha = p.AlphaAtTime(time); - // todo: implement per particle. - var rect = new RectangleF(pos.X * sourceSize.X, pos.Y * sourceSize.Y, Texture.DisplayWidth, Texture.DisplayHeight); + var rect = new RectangleF( + pos.X * sourceSize.X - Texture.DisplayWidth / 2, + pos.Y * sourceSize.Y - Texture.DisplayHeight / 2, + Texture.DisplayWidth, + Texture.DisplayHeight); + // convert to screen space. var quad = new Quad( Vector2Extensions.Transform(rect.TopLeft, DrawInfo.Matrix), Vector2Extensions.Transform(rect.TopRight, DrawInfo.Matrix), @@ -96,7 +104,7 @@ namespace osu.Game.Graphics Vector2Extensions.Transform(rect.BottomRight, DrawInfo.Matrix) ); - DrawQuad(Texture, quad, DrawColourInfo.Colour, null, vertexAction, + DrawQuad(Texture, quad, DrawColourInfo.Colour.MultiplyAlpha(alpha), null, vertexAction, new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height), null, TextureCoords); } @@ -105,22 +113,31 @@ namespace osu.Game.Graphics private class ParticlePart { - private readonly double totalDuration; + private readonly double availableDuration; private double duration; private double direction; private float distance; - public ParticlePart(double totalDuration) + public ParticlePart(double availableDuration) { - this.totalDuration = totalDuration; + this.availableDuration = availableDuration; Randomise(); } + public void Randomise() + { + distance = RNG.NextSingle(0.5f); + duration = RNG.NextDouble(availableDuration / 3, availableDuration); + direction = RNG.NextSingle(0, MathF.PI * 2); + } + + public float AlphaAtTime(double time) => 1 - progressAtTime(time); + public Vector2 PositionAtTime(double time) { - return new Vector2(0.5f) + positionForOffset(distance * (float)(time / duration)); + return new Vector2(0.5f) + positionForOffset(distance * progressAtTime(time)); Vector2 positionForOffset(float offset) => new Vector2( (float)(offset * Math.Sin(direction)), @@ -128,12 +145,7 @@ namespace osu.Game.Graphics ); } - public void Randomise() - { - distance = RNG.NextSingle(0.5f); - duration = RNG.NextDouble(totalDuration / 3, totalDuration); - direction = RNG.NextSingle(0, MathF.PI * 2); - } + private float progressAtTime(double time) => (float)Math.Clamp(time / duration, 0, 1); } } } From 84e73e88d57c3359be76e905860e20f31122291d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 18:05:41 +0900 Subject: [PATCH 074/394] Use structs for parts for added safety --- osu.Game/Graphics/ParticleExplosion.cs | 51 +++++++++++--------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index 68dedf70a0..fa55260f53 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -13,8 +13,12 @@ using osuTK; namespace osu.Game.Graphics { + /// + /// An explosion of textured particles based on how osu-stable randomises the explosion pattern. + /// public class ParticleExplosion : Sprite { + private readonly int particleCount; private readonly double duration; private double startTime; @@ -23,11 +27,9 @@ namespace osu.Game.Graphics public ParticleExplosion(Texture texture, int particleCount, double duration) { Texture = texture; + this.particleCount = particleCount; this.duration = duration; Blending = BlendingParameters.Additive; - - for (int i = 0; i < particleCount; i++) - parts.Add(new ParticlePart(duration)); } protected override void LoadComplete() @@ -36,19 +38,23 @@ namespace osu.Game.Graphics Restart(); } + /// + /// Restart the animation from the current point in time. + /// Supports transform time offset chaining. + /// public void Restart() { startTime = TransformStartTime; this.FadeOutFromOne(duration); - foreach (var p in parts) - p.Randomise(); + parts.Clear(); + for (int i = 0; i < particleCount; i++) + parts.Add(new ParticlePart(duration)); } protected override void Update() { base.Update(); - Invalidate(Invalidation.DrawNode); } @@ -56,7 +62,7 @@ namespace osu.Game.Graphics private class ParticleExplosionDrawNode : SpriteDrawNode { - private List parts = new List(); + private readonly List parts = new List(); private ParticleExplosion source => (ParticleExplosion)Source; @@ -73,9 +79,9 @@ namespace osu.Game.Graphics { base.ApplyState(); - // this is mostly safe as the parts are immutable. - // the most that can go wrong is the random state be incorrect - parts = source.parts; + parts.Clear(); + parts.AddRange(source.parts); + sourceSize = source.Size; startTime = source.startTime; currentTime = source.Time.Current; @@ -111,22 +117,13 @@ namespace osu.Game.Graphics } } - private class ParticlePart + private readonly struct ParticlePart { - private readonly double availableDuration; - - private double duration; - private double direction; - private float distance; + private readonly double duration; + private readonly double direction; + private readonly float distance; public ParticlePart(double availableDuration) - { - this.availableDuration = availableDuration; - - Randomise(); - } - - public void Randomise() { distance = RNG.NextSingle(0.5f); duration = RNG.NextDouble(availableDuration / 3, availableDuration); @@ -137,12 +134,8 @@ namespace osu.Game.Graphics public Vector2 PositionAtTime(double time) { - return new Vector2(0.5f) + positionForOffset(distance * progressAtTime(time)); - - Vector2 positionForOffset(float offset) => new Vector2( - (float)(offset * Math.Sin(direction)), - (float)(offset * Math.Cos(direction)) - ); + var travelledDistance = distance * progressAtTime(time); + return new Vector2(0.5f) + travelledDistance * new Vector2((float)Math.Sin(direction), (float)Math.Cos(direction)); } private float progressAtTime(double time) => (float)Math.Clamp(time / duration, 0, 1); From dd5b90cf6cde83e0b59f282f0bdb1aa75e30a6d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 18:07:41 +0900 Subject: [PATCH 075/394] Add test coverage of animation restarting --- .../Visual/Gameplay/TestSceneParticleExplosion.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs index 63c8757afd..82095cb809 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs @@ -13,17 +13,26 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneParticleExplosion : OsuTestScene { + private ParticleExplosion explosion; + [BackgroundDependencyLoader] private void load(TextureStore textures) { - AddRepeatStep(@"display", () => + AddStep("create initial", () => { - Child = new ParticleExplosion(textures.Get("Cursor/cursortrail"), 150, 1200) + Child = explosion = new ParticleExplosion(textures.Get("Cursor/cursortrail"), 150, 1200) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(400) }; + }); + + AddWaitStep("wait for playback", 5); + + AddRepeatStep(@"restart animation", () => + { + explosion.Restart(); }, 10); } } From 1c7ee2ca5fdaa87f2ec487bbe92466dce16560b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 18:46:19 +0900 Subject: [PATCH 076/394] Simplify math by making direction a float --- osu.Game/Graphics/ParticleExplosion.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index fa55260f53..e0d2b50c55 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -120,7 +120,7 @@ namespace osu.Game.Graphics private readonly struct ParticlePart { private readonly double duration; - private readonly double direction; + private readonly float direction; private readonly float distance; public ParticlePart(double availableDuration) @@ -135,7 +135,7 @@ namespace osu.Game.Graphics public Vector2 PositionAtTime(double time) { var travelledDistance = distance * progressAtTime(time); - return new Vector2(0.5f) + travelledDistance * new Vector2((float)Math.Sin(direction), (float)Math.Cos(direction)); + return new Vector2(0.5f) + travelledDistance * new Vector2(MathF.Sin(direction), MathF.Cos(direction)); } private float progressAtTime(double time) => (float)Math.Clamp(time / duration, 0, 1); From 730b14b5bb102572c742b769e168c48febd9791c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Nov 2020 19:51:09 +0900 Subject: [PATCH 077/394] Add initial hit sample pooling --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 13 +- osu.Game/Audio/HitSampleInfo.cs | 15 +- osu.Game/Audio/SampleInfo.cs | 15 +- .../Objects/Drawables/DrawableHitObject.cs | 12 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 29 ++- osu.Game/Rulesets/UI/Playfield.cs | 38 +++- osu.Game/Skinning/PausableSkinnableSound.cs | 8 +- osu.Game/Skinning/SkinReloadableDrawable.cs | 4 +- osu.Game/Skinning/SkinnableSound.cs | 203 +++++++++++++++--- .../Drawables/DrawableStoryboardSample.cs | 4 +- 10 files changed, 283 insertions(+), 58 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index 4ecfb7b16d..d61a88b7df 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using osu.Game.Audio; using osu.Game.Rulesets.Catch.Judgements; @@ -26,11 +27,17 @@ namespace osu.Game.Rulesets.Catch.Objects Samples = samples; } - private class BananaHitSampleInfo : HitSampleInfo + private class BananaHitSampleInfo : HitSampleInfo, IEquatable { - private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" }; + private static readonly string[] lookup_names = { "metronomelow", "catch-banana" }; - public override IEnumerable LookupNames => lookupNames; + public override IEnumerable LookupNames => lookup_names; + + public bool Equals(BananaHitSampleInfo other) => true; + + public override bool Equals(object obj) => obj is BananaHitSampleInfo other && Equals(other); + + public override int GetHashCode() => lookup_names.GetHashCode(); } } } diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 8efaeb3795..46f0abd7b7 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace osu.Game.Audio { @@ -10,7 +11,7 @@ namespace osu.Game.Audio /// Describes a gameplay hit sample. /// [Serializable] - public class HitSampleInfo : ISampleInfo + public class HitSampleInfo : ISampleInfo, IEquatable { public const string HIT_WHISTLE = @"hitwhistle"; public const string HIT_FINISH = @"hitfinish"; @@ -57,5 +58,17 @@ namespace osu.Game.Audio } public HitSampleInfo Clone() => (HitSampleInfo)MemberwiseClone(); + + public bool Equals(HitSampleInfo other) + => other != null && Bank == other.Bank && Name == other.Name && Suffix == other.Suffix; + + public override bool Equals(object obj) + => obj is HitSampleInfo other && Equals(other); + + [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] // This will have to be addressed eventually + public override int GetHashCode() + { + return HashCode.Combine(Bank, Name, Suffix); + } } } diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 240d70c418..221bc31639 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -1,14 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Audio { /// /// Describes a gameplay sample. /// - public class SampleInfo : ISampleInfo + public class SampleInfo : ISampleInfo, IEquatable { private readonly string[] sampleNames; @@ -20,5 +22,16 @@ namespace osu.Game.Audio public IEnumerable LookupNames => sampleNames; public int Volume { get; } = 100; + + public override int GetHashCode() + { + return HashCode.Combine(sampleNames, Volume); + } + + public bool Equals(SampleInfo other) + => other != null && sampleNames.SequenceEqual(other.sampleNames); + + public override bool Equals(object obj) + => obj is SampleInfo other && Equals(other); } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ca49ed9e75..0d97066b35 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -10,7 +10,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Logging; using osu.Framework.Threading; @@ -139,8 +138,6 @@ namespace osu.Game.Rulesets.Objects.Drawables [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } - private Container samplesContainer; - /// /// Creates a new . /// @@ -159,7 +156,7 @@ namespace osu.Game.Rulesets.Objects.Drawables config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds); // Explicit non-virtual function call. - base.AddInternal(samplesContainer = new Container { RelativeSizeAxes = Axes.Both }); + base.AddInternal(Samples = new PausableSkinnableSound(Array.Empty())); } protected override void LoadAsyncComplete() @@ -269,6 +266,8 @@ namespace osu.Game.Rulesets.Objects.Drawables // In order to stop this needless update, the event is unbound and re-bound as late as possible in Apply(). samplesBindable.CollectionChanged -= onSamplesChanged; + Samples.Samples = Array.Empty(); + if (nestedHitObjects.IsValueCreated) { foreach (var obj in nestedHitObjects.Value) @@ -335,8 +334,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected virtual void LoadSamples() { - samplesContainer.Clear(); - Samples = null; + Samples.Samples = Array.Empty(); var samples = GetSamples().ToArray(); @@ -349,7 +347,7 @@ namespace osu.Game.Rulesets.Objects.Drawables + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); } - samplesContainer.Add(Samples = new PausableSkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)))); + Samples.Samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); } private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples(); diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 44b22033dc..19d573a55a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -5,6 +5,7 @@ using osuTK; using osu.Game.Rulesets.Objects.Types; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using osu.Game.Beatmaps.Formats; using osu.Game.Audio; @@ -500,7 +501,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); } - public class LegacyHitSampleInfo : HitSampleInfo + public class LegacyHitSampleInfo : HitSampleInfo, IEquatable { private int customSampleBank; @@ -524,9 +525,21 @@ namespace osu.Game.Rulesets.Objects.Legacy /// using the skin config option. /// public bool IsLayered { get; set; } + + public bool Equals(LegacyHitSampleInfo other) + => other != null && base.Equals(other) && CustomSampleBank == other.CustomSampleBank; + + public override bool Equals(object obj) + => obj is LegacyHitSampleInfo other && Equals(other); + + [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] // This will have to be addressed eventually + public override int GetHashCode() + { + return HashCode.Combine(base.GetHashCode(), customSampleBank); + } } - private class FileHitSampleInfo : LegacyHitSampleInfo + private class FileHitSampleInfo : LegacyHitSampleInfo, IEquatable { public string Filename; @@ -542,6 +555,18 @@ namespace osu.Game.Rulesets.Objects.Legacy Filename, Path.ChangeExtension(Filename, null) }; + + public bool Equals(FileHitSampleInfo other) + => other != null && Filename == other.Filename; + + public override bool Equals(object obj) + => obj is FileHitSampleInfo other && Equals(other); + + [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] // This will have to be addressed eventually + public override int GetHashCode() + { + return HashCode.Combine(Filename); + } } } } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 82ec653f31..9f3a4c508f 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -8,19 +8,23 @@ using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; 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.Skinning; using osuTK; namespace osu.Game.Rulesets.UI { [Cached(typeof(IPooledHitObjectProvider))] - public abstract class Playfield : CompositeDrawable, IPooledHitObjectProvider + [Cached(typeof(IPooledSampleProvider))] + public abstract class Playfield : CompositeDrawable, IPooledHitObjectProvider, IPooledSampleProvider { /// /// Invoked when a is judged. @@ -80,6 +84,12 @@ namespace osu.Game.Rulesets.UI /// public readonly BindableBool DisplayJudgements = new BindableBool(true); + [Resolved(CanBeNull = true)] + private IReadOnlyList mods { get; set; } + + [Resolved] + private ISampleStore sampleStore { get; set; } + /// /// Creates a new . /// @@ -96,9 +106,6 @@ namespace osu.Game.Rulesets.UI })); } - [Resolved(CanBeNull = true)] - private IReadOnlyList mods { get; set; } - [BackgroundDependencyLoader] private void load() { @@ -336,6 +343,29 @@ namespace osu.Game.Rulesets.UI }); } + private readonly Dictionary> samplePools = new Dictionary>(); + + public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo) + { + if (!samplePools.TryGetValue(sampleInfo, out var existingPool)) + samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 5); + + return existingPool.Get(); + } + + private class DrawableSamplePool : DrawablePool + { + private readonly ISampleInfo sampleInfo; + + public DrawableSamplePool(ISampleInfo sampleInfo, int initialSize, int? maximumSize = null) + : base(initialSize, maximumSize) + { + this.sampleInfo = sampleInfo; + } + + protected override PoolableSkinnableSample CreateNewDrawable() => base.CreateNewDrawable().With(d => d.Apply(sampleInfo)); + } + #endregion #region Editor logic diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index 4f09aec0b6..758b784649 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -14,13 +14,13 @@ namespace osu.Game.Skinning { protected bool RequestedPlaying { get; private set; } - public PausableSkinnableSound(ISampleInfo hitSamples) - : base(hitSamples) + public PausableSkinnableSound(ISampleInfo sample) + : base(sample) { } - public PausableSkinnableSound(IEnumerable hitSamples) - : base(hitSamples) + public PausableSkinnableSound(IEnumerable samples) + : base(samples) { } diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs index cc9cbf7b59..50b4143375 100644 --- a/osu.Game/Skinning/SkinReloadableDrawable.cs +++ b/osu.Game/Skinning/SkinReloadableDrawable.cs @@ -27,7 +27,7 @@ namespace osu.Game.Skinning /// /// Whether fallback to default skin should be allowed if the custom skin is missing this resource. /// - private bool allowDefaultFallback => allowFallback == null || allowFallback.Invoke(CurrentSkin); + protected bool AllowDefaultFallback => allowFallback == null || allowFallback.Invoke(CurrentSkin); /// /// Create a new @@ -58,7 +58,7 @@ namespace osu.Game.Skinning private void skinChanged() { - SkinChanged(CurrentSkin, allowDefaultFallback); + SkinChanged(CurrentSkin, AllowDefaultFallback); OnSkinChanged?.Invoke(); } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index ffa0a963ce..8410b7eeae 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -1,26 +1,149 @@ // 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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Game.Audio; namespace osu.Game.Skinning { - public class SkinnableSound : SkinReloadableDrawable, IAdjustableAudioComponent + public interface IPooledSampleProvider { - private readonly ISampleInfo[] hitSamples; + [CanBeNull] + PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo); + } + + public class PoolableSkinnableSample : SkinReloadableDrawable, IAggregateAudioAdjustment, IAdjustableAudioComponent + { + private ISampleInfo sampleInfo; + private DrawableSample sample; [Resolved] - private ISampleStore samples { get; set; } + private ISampleStore sampleStore { get; set; } + [Cached] + private readonly AudioAdjustments adjustments = new AudioAdjustments(); + + public PoolableSkinnableSample() + { + } + + public PoolableSkinnableSample(ISampleInfo sampleInfo) + { + Apply(sampleInfo); + } + + public void Apply(ISampleInfo sampleInfo) + { + if (this.sampleInfo != null) + throw new InvalidOperationException($"A {nameof(PoolableSkinnableSample)} cannot be applied multiple {nameof(ISampleInfo)}s."); + + this.sampleInfo = sampleInfo; + + if (LoadState >= LoadState.Ready) + updateSample(); + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + updateSample(); + } + + private void updateSample() + { + ClearInternal(); + + var ch = CurrentSkin.GetSample(sampleInfo); + + if (ch == null && AllowDefaultFallback) + { + foreach (var lookup in sampleInfo.LookupNames) + { + if ((ch = sampleStore.Get(lookup)) != null) + break; + } + } + + if (ch == null) + return; + + AddInternal(sample = new DrawableSample(ch) + { + Looping = Looping, + Volume = { Value = sampleInfo.Volume / 100.0 } + }); + } + + public void Play(bool restart = true) => sample?.Play(restart); + + public void Stop() => sample?.Stop(); + + public bool Playing => sample?.Playing ?? false; + + private bool looping; + + public bool Looping + { + get => looping; + set + { + looping = value; + + if (sample != null) + sample.Looping = value; + } + } + + /// + /// The volume of this component. + /// + public BindableNumber Volume => adjustments.Volume; + + /// + /// The playback balance of this sample (-1 .. 1 where 0 is centered) + /// + public BindableNumber Balance => adjustments.Balance; + + /// + /// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency. + /// + public BindableNumber Frequency => adjustments.Frequency; + + /// + /// Rate at which the component is played back (does not affect pitch). 1 is 100% playback speed. + /// + public BindableNumber Tempo => adjustments.Tempo; + + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) + => adjustments.AddAdjustment(type, adjustBindable); + + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) + => adjustments.RemoveAdjustment(type, adjustBindable); + + public void RemoveAllAdjustments(AdjustableProperty type) => adjustments.RemoveAllAdjustments(type); + + public IBindable AggregateVolume => adjustments.AggregateVolume; + + public IBindable AggregateBalance => adjustments.AggregateBalance; + + public IBindable AggregateFrequency => adjustments.AggregateFrequency; + + public IBindable AggregateTempo => adjustments.AggregateTempo; + } + + public class SkinnableSound : SkinReloadableDrawable, IAdjustableAudioComponent + { public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; @@ -34,17 +157,44 @@ namespace osu.Game.Skinning /// protected bool PlayWhenZeroVolume => Looping; - protected readonly AudioContainer SamplesContainer; + protected readonly AudioContainer SamplesContainer; - public SkinnableSound(ISampleInfo hitSamples) - : this(new[] { hitSamples }) + [Resolved] + private ISampleStore sampleStore { get; set; } + + [Resolved(CanBeNull = true)] + private IPooledSampleProvider pooledProvider { get; set; } + + public SkinnableSound(ISampleInfo sample) + : this(new[] { sample }) { } - public SkinnableSound(IEnumerable hitSamples) + public SkinnableSound(IEnumerable samples) { - this.hitSamples = hitSamples.ToArray(); - InternalChild = SamplesContainer = new AudioContainer(); + this.samples = samples.ToArray(); + + InternalChild = SamplesContainer = new AudioContainer(); + } + + private ISampleInfo[] samples; + + public ISampleInfo[] Samples + { + get => samples; + set + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (samples == value) + return; + + samples = value; + + if (LoadState >= LoadState.Ready) + updateSamples(); + } } private bool looping; @@ -77,34 +227,23 @@ namespace osu.Game.Skinning } protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + // Start playback internally for the new samples if the previous ones were playing beforehand. + if (IsPlaying) + Play(); + } + + private void updateSamples() { bool wasPlaying = IsPlaying; - var channels = hitSamples.Select(s => - { - var ch = skin.GetSample(s); + // Remove all pooled samples (return them to the pool), and dispose the rest. + SamplesContainer.RemoveAll(s => s.IsInPool); + SamplesContainer.Clear(); - if (ch == null && allowFallback) - { - foreach (var lookup in s.LookupNames) - { - if ((ch = samples.Get(lookup)) != null) - break; - } - } + foreach (var s in samples) + SamplesContainer.Add(pooledProvider?.GetPooledSample(s) ?? new PoolableSkinnableSample(s)); - if (ch != null) - { - ch.Looping = looping; - ch.Volume.Value = s.Volume / 100.0; - } - - return ch; - }).Where(c => c != null); - - SamplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c)); - - // Start playback internally for the new samples if the previous ones were playing beforehand. if (wasPlaying) Play(); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 08811b9b8c..b8d212d3c9 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -37,8 +37,8 @@ namespace osu.Game.Storyboards.Drawables foreach (var mod in mods.Value.OfType()) { - foreach (var sample in SamplesContainer) - mod.ApplyToSample(sample); + // foreach (var sample in SamplesContainer) + // mod.ApplyToSample(sample.Sample); } } From 003fed857c9f35c258f6ad1499d8e5bc4e624827 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Nov 2020 19:52:34 +0900 Subject: [PATCH 078/394] Separate files --- osu.Game/Skinning/IPooledSampleProvider.cs | 14 ++ osu.Game/Skinning/PoolableSkinnableSample.cs | 133 +++++++++++++++++++ osu.Game/Skinning/SkinnableSound.cs | 127 ------------------ 3 files changed, 147 insertions(+), 127 deletions(-) create mode 100644 osu.Game/Skinning/IPooledSampleProvider.cs create mode 100644 osu.Game/Skinning/PoolableSkinnableSample.cs diff --git a/osu.Game/Skinning/IPooledSampleProvider.cs b/osu.Game/Skinning/IPooledSampleProvider.cs new file mode 100644 index 0000000000..3dc0b5375d --- /dev/null +++ b/osu.Game/Skinning/IPooledSampleProvider.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Game.Audio; + +namespace osu.Game.Skinning +{ + public interface IPooledSampleProvider + { + [CanBeNull] + PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo); + } +} diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs new file mode 100644 index 0000000000..6ad7345954 --- /dev/null +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -0,0 +1,133 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; +using osu.Game.Audio; + +namespace osu.Game.Skinning +{ + public class PoolableSkinnableSample : SkinReloadableDrawable, IAggregateAudioAdjustment, IAdjustableAudioComponent + { + private ISampleInfo sampleInfo; + private DrawableSample sample; + + [Resolved] + private ISampleStore sampleStore { get; set; } + + [Cached] + private readonly AudioAdjustments adjustments = new AudioAdjustments(); + + public PoolableSkinnableSample() + { + } + + public PoolableSkinnableSample(ISampleInfo sampleInfo) + { + Apply(sampleInfo); + } + + public void Apply(ISampleInfo sampleInfo) + { + if (this.sampleInfo != null) + throw new InvalidOperationException($"A {nameof(PoolableSkinnableSample)} cannot be applied multiple {nameof(ISampleInfo)}s."); + + this.sampleInfo = sampleInfo; + + if (LoadState >= LoadState.Ready) + updateSample(); + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + updateSample(); + } + + private void updateSample() + { + ClearInternal(); + + var ch = CurrentSkin.GetSample(sampleInfo); + + if (ch == null && AllowDefaultFallback) + { + foreach (var lookup in sampleInfo.LookupNames) + { + if ((ch = sampleStore.Get(lookup)) != null) + break; + } + } + + if (ch == null) + return; + + AddInternal(sample = new DrawableSample(ch) + { + Looping = Looping, + Volume = { Value = sampleInfo.Volume / 100.0 } + }); + } + + public void Play(bool restart = true) => sample?.Play(restart); + + public void Stop() => sample?.Stop(); + + public bool Playing => sample?.Playing ?? false; + + private bool looping; + + public bool Looping + { + get => looping; + set + { + looping = value; + + if (sample != null) + sample.Looping = value; + } + } + + /// + /// The volume of this component. + /// + public BindableNumber Volume => adjustments.Volume; + + /// + /// The playback balance of this sample (-1 .. 1 where 0 is centered) + /// + public BindableNumber Balance => adjustments.Balance; + + /// + /// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency. + /// + public BindableNumber Frequency => adjustments.Frequency; + + /// + /// Rate at which the component is played back (does not affect pitch). 1 is 100% playback speed. + /// + public BindableNumber Tempo => adjustments.Tempo; + + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) + => adjustments.AddAdjustment(type, adjustBindable); + + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) + => adjustments.RemoveAdjustment(type, adjustBindable); + + public void RemoveAllAdjustments(AdjustableProperty type) => adjustments.RemoveAllAdjustments(type); + + public IBindable AggregateVolume => adjustments.AggregateVolume; + + public IBindable AggregateBalance => adjustments.AggregateBalance; + + public IBindable AggregateFrequency => adjustments.AggregateFrequency; + + public IBindable AggregateTempo => adjustments.AggregateTempo; + } +} diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 8410b7eeae..77ae8f1e16 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -4,144 +4,17 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Game.Audio; namespace osu.Game.Skinning { - public interface IPooledSampleProvider - { - [CanBeNull] - PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo); - } - - public class PoolableSkinnableSample : SkinReloadableDrawable, IAggregateAudioAdjustment, IAdjustableAudioComponent - { - private ISampleInfo sampleInfo; - private DrawableSample sample; - - [Resolved] - private ISampleStore sampleStore { get; set; } - - [Cached] - private readonly AudioAdjustments adjustments = new AudioAdjustments(); - - public PoolableSkinnableSample() - { - } - - public PoolableSkinnableSample(ISampleInfo sampleInfo) - { - Apply(sampleInfo); - } - - public void Apply(ISampleInfo sampleInfo) - { - if (this.sampleInfo != null) - throw new InvalidOperationException($"A {nameof(PoolableSkinnableSample)} cannot be applied multiple {nameof(ISampleInfo)}s."); - - this.sampleInfo = sampleInfo; - - if (LoadState >= LoadState.Ready) - updateSample(); - } - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - updateSample(); - } - - private void updateSample() - { - ClearInternal(); - - var ch = CurrentSkin.GetSample(sampleInfo); - - if (ch == null && AllowDefaultFallback) - { - foreach (var lookup in sampleInfo.LookupNames) - { - if ((ch = sampleStore.Get(lookup)) != null) - break; - } - } - - if (ch == null) - return; - - AddInternal(sample = new DrawableSample(ch) - { - Looping = Looping, - Volume = { Value = sampleInfo.Volume / 100.0 } - }); - } - - public void Play(bool restart = true) => sample?.Play(restart); - - public void Stop() => sample?.Stop(); - - public bool Playing => sample?.Playing ?? false; - - private bool looping; - - public bool Looping - { - get => looping; - set - { - looping = value; - - if (sample != null) - sample.Looping = value; - } - } - - /// - /// The volume of this component. - /// - public BindableNumber Volume => adjustments.Volume; - - /// - /// The playback balance of this sample (-1 .. 1 where 0 is centered) - /// - public BindableNumber Balance => adjustments.Balance; - - /// - /// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency. - /// - public BindableNumber Frequency => adjustments.Frequency; - - /// - /// Rate at which the component is played back (does not affect pitch). 1 is 100% playback speed. - /// - public BindableNumber Tempo => adjustments.Tempo; - - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => adjustments.AddAdjustment(type, adjustBindable); - - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => adjustments.RemoveAdjustment(type, adjustBindable); - - public void RemoveAllAdjustments(AdjustableProperty type) => adjustments.RemoveAllAdjustments(type); - - public IBindable AggregateVolume => adjustments.AggregateVolume; - - public IBindable AggregateBalance => adjustments.AggregateBalance; - - public IBindable AggregateFrequency => adjustments.AggregateFrequency; - - public IBindable AggregateTempo => adjustments.AggregateTempo; - } - public class SkinnableSound : SkinReloadableDrawable, IAdjustableAudioComponent { public override bool RemoveWhenNotAlive => false; From 8920534a255fe7c182a33d3200a8c23dd177e299 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Nov 2020 20:24:03 +0900 Subject: [PATCH 079/394] Fix pools not being added to hierarchy --- osu.Game/Rulesets/UI/Playfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 9f3a4c508f..5383c4b2ce 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -348,7 +348,7 @@ namespace osu.Game.Rulesets.UI public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo) { if (!samplePools.TryGetValue(sampleInfo, out var existingPool)) - samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 5); + AddInternal(samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 5)); return existingPool.Get(); } From 812d5d59b1ac12757a86cb44bd6447b9f7d60846 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Nov 2020 20:29:09 +0900 Subject: [PATCH 080/394] Fix looping not being propagated --- osu.Game/Skinning/SkinnableSound.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 77ae8f1e16..eca1b9b03f 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -115,7 +115,12 @@ namespace osu.Game.Skinning SamplesContainer.Clear(); foreach (var s in samples) - SamplesContainer.Add(pooledProvider?.GetPooledSample(s) ?? new PoolableSkinnableSample(s)); + { + var sample = pooledProvider?.GetPooledSample(s) ?? new PoolableSkinnableSample(s); + sample.Looping = Looping; + + SamplesContainer.Add(sample); + } if (wasPlaying) Play(); From 70cb1979578e51fda79a41b12f2767777d64cf08 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Nov 2020 20:38:36 +0900 Subject: [PATCH 081/394] Cleanups --- .../Objects/Drawables/DrawableHitObject.cs | 7 +++---- osu.Game/Skinning/PausableSkinnableSound.cs | 11 ++++++++--- osu.Game/Skinning/SkinnableSound.cs | 15 ++++++++++----- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 0d97066b35..26d2ffe3ce 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Objects.Drawables config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds); // Explicit non-virtual function call. - base.AddInternal(Samples = new PausableSkinnableSound(Array.Empty())); + base.AddInternal(Samples = new PausableSkinnableSound()); } protected override void LoadAsyncComplete() @@ -266,7 +266,8 @@ namespace osu.Game.Rulesets.Objects.Drawables // In order to stop this needless update, the event is unbound and re-bound as late as possible in Apply(). samplesBindable.CollectionChanged -= onSamplesChanged; - Samples.Samples = Array.Empty(); + // Release the samples for other hitobjects to use. + Samples.Samples = null; if (nestedHitObjects.IsValueCreated) { @@ -334,8 +335,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected virtual void LoadSamples() { - Samples.Samples = Array.Empty(); - var samples = GetSamples().ToArray(); if (samples.Length <= 0) diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index 758b784649..be4664356d 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Threading; @@ -14,16 +15,20 @@ namespace osu.Game.Skinning { protected bool RequestedPlaying { get; private set; } - public PausableSkinnableSound(ISampleInfo sample) - : base(sample) + public PausableSkinnableSound() { } - public PausableSkinnableSound(IEnumerable samples) + public PausableSkinnableSound([NotNull] IEnumerable samples) : base(samples) { } + public PausableSkinnableSound([NotNull] ISampleInfo sample) + : base(sample) + { + } + private readonly IBindable samplePlaybackDisabled = new Bindable(); private ScheduledDelegate scheduledStart; diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index eca1b9b03f..67d13118d9 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -38,18 +39,23 @@ namespace osu.Game.Skinning [Resolved(CanBeNull = true)] private IPooledSampleProvider pooledProvider { get; set; } - public SkinnableSound(ISampleInfo sample) - : this(new[] { sample }) + public SkinnableSound() { } - public SkinnableSound(IEnumerable samples) + public SkinnableSound([NotNull] IEnumerable samples) + : this() { this.samples = samples.ToArray(); InternalChild = SamplesContainer = new AudioContainer(); } + public SkinnableSound([NotNull] ISampleInfo sample) + : this(new[] { sample }) + { + } + private ISampleInfo[] samples; public ISampleInfo[] Samples @@ -57,8 +63,7 @@ namespace osu.Game.Skinning get => samples; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + value ??= Array.Empty(); if (samples == value) return; From f013928fa3dd7c0293cef09c8c2b651ea2e8e314 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Nov 2020 20:40:01 +0900 Subject: [PATCH 082/394] Set maximum pool size --- osu.Game/Rulesets/UI/Playfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 5383c4b2ce..6e89f20246 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -348,7 +348,7 @@ namespace osu.Game.Rulesets.UI public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo) { if (!samplePools.TryGetValue(sampleInfo, out var existingPool)) - AddInternal(samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 5)); + AddInternal(samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 1, 30)); return existingPool.Get(); } From 688a04c2ff8e6a931b67ed4a36337508f5555d22 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Nov 2020 20:40:30 +0900 Subject: [PATCH 083/394] Make slider/spinner use pooled samples --- .../Objects/Drawables/DrawableSlider.cs | 17 ++++------- .../Objects/Drawables/DrawableSpinner.cs | 29 +++++++++++-------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 14c494d909..b62c04eed9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; +using osu.Game.Audio; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.UI; @@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Container tailContainer; private Container tickContainer; private Container repeatContainer; - private Container samplesContainer; + private PausableSkinnableSound slidingSample; public DrawableSlider() : this(null) @@ -69,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Alpha = 0 }, headContainer = new Container { RelativeSizeAxes = Axes.Both }, - samplesContainer = new Container { RelativeSizeAxes = Axes.Both } + slidingSample = new PausableSkinnableSound { Looping = true } }; PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); @@ -100,17 +101,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.OnFree(hitObject); PathVersion.UnbindFrom(HitObject.Path.Version); - } - private PausableSkinnableSound slidingSample; + slidingSample.Samples = null; + } protected override void LoadSamples() { base.LoadSamples(); - samplesContainer.Clear(); - slidingSample = null; - var firstSample = HitObject.Samples.FirstOrDefault(); if (firstSample != null) @@ -118,10 +116,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); clone.Name = "sliderslide"; - samplesContainer.Add(slidingSample = new PausableSkinnableSound(clone) - { - Looping = true - }); + slidingSample.Samples = new ISampleInfo[] { clone }; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 2a14a7c975..e5fc717504 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -9,6 +9,7 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Container ticks; private SpinnerBonusDisplay bonusDisplay; - private Container samplesContainer; + private PausableSkinnableSound spinningSample; private Bindable isSpinning; private bool spinnerFrequencyModulate; @@ -81,7 +82,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre, Y = -120, }, - samplesContainer = new Container { RelativeSizeAxes = Axes.Both } + spinningSample = new PausableSkinnableSound + { + Volume = { Value = 0 }, + Looping = true, + Frequency = { Value = spinning_sample_initial_frequency } + } }; PositionBindable.BindValueChanged(pos => Position = pos.NewValue); @@ -95,17 +101,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables isSpinning.BindValueChanged(updateSpinningSample); } - private PausableSkinnableSound spinningSample; private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; + protected override void OnFree(HitObject hitObject) + { + base.OnFree(hitObject); + + spinningSample.Samples = null; + } + protected override void LoadSamples() { base.LoadSamples(); - samplesContainer.Clear(); - spinningSample = null; - var firstSample = HitObject.Samples.FirstOrDefault(); if (firstSample != null) @@ -113,12 +122,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); clone.Name = "spinnerspin"; - samplesContainer.Add(spinningSample = new PausableSkinnableSound(clone) - { - Volume = { Value = 0 }, - Looping = true, - Frequency = { Value = spinning_sample_initial_frequency } - }); + spinningSample.Samples = new ISampleInfo[] { clone }; + spinningSample.Frequency.Value = spinning_sample_initial_frequency; } } From 0287269b2fed663d910f9571042accfe3a11d185 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Nov 2020 21:01:38 +0900 Subject: [PATCH 084/394] Fix volume discrepancies --- osu.Game/Skinning/PoolableSkinnableSample.cs | 42 ++++++++++---------- osu.Game/Skinning/SkinnableSound.cs | 4 +- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 6ad7345954..ad799dd32e 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -8,26 +8,28 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; +using osu.Framework.Graphics.Containers; using osu.Game.Audio; namespace osu.Game.Skinning { public class PoolableSkinnableSample : SkinReloadableDrawable, IAggregateAudioAdjustment, IAdjustableAudioComponent { + private readonly AudioContainer sampleContainer; + private ISampleInfo sampleInfo; private DrawableSample sample; [Resolved] private ISampleStore sampleStore { get; set; } - [Cached] - private readonly AudioAdjustments adjustments = new AudioAdjustments(); - public PoolableSkinnableSample() { + InternalChild = sampleContainer = new AudioContainer { RelativeSizeAxes = Axes.Both }; } public PoolableSkinnableSample(ISampleInfo sampleInfo) + : this() { Apply(sampleInfo); } @@ -39,6 +41,8 @@ namespace osu.Game.Skinning this.sampleInfo = sampleInfo; + Volume.Value = sampleInfo.Volume / 100.0; + if (LoadState >= LoadState.Ready) updateSample(); } @@ -51,7 +55,7 @@ namespace osu.Game.Skinning private void updateSample() { - ClearInternal(); + sampleContainer.Clear(); var ch = CurrentSkin.GetSample(sampleInfo); @@ -67,11 +71,7 @@ namespace osu.Game.Skinning if (ch == null) return; - AddInternal(sample = new DrawableSample(ch) - { - Looping = Looping, - Volume = { Value = sampleInfo.Volume / 100.0 } - }); + sampleContainer.Add(sample = new DrawableSample(ch) { Looping = Looping }); } public void Play(bool restart = true) => sample?.Play(restart); @@ -97,37 +97,35 @@ namespace osu.Game.Skinning /// /// The volume of this component. /// - public BindableNumber Volume => adjustments.Volume; + public BindableNumber Volume => sampleContainer.Volume; /// /// The playback balance of this sample (-1 .. 1 where 0 is centered) /// - public BindableNumber Balance => adjustments.Balance; + public BindableNumber Balance => sampleContainer.Balance; /// /// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency. /// - public BindableNumber Frequency => adjustments.Frequency; + public BindableNumber Frequency => sampleContainer.Frequency; /// /// Rate at which the component is played back (does not affect pitch). 1 is 100% playback speed. /// - public BindableNumber Tempo => adjustments.Tempo; + public BindableNumber Tempo => sampleContainer.Tempo; - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => adjustments.AddAdjustment(type, adjustBindable); + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => adjustments.RemoveAdjustment(type, adjustBindable); + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable); - public void RemoveAllAdjustments(AdjustableProperty type) => adjustments.RemoveAllAdjustments(type); + public void RemoveAllAdjustments(AdjustableProperty type) => sampleContainer.RemoveAllAdjustments(type); - public IBindable AggregateVolume => adjustments.AggregateVolume; + public IBindable AggregateVolume => sampleContainer.AggregateVolume; - public IBindable AggregateBalance => adjustments.AggregateBalance; + public IBindable AggregateBalance => sampleContainer.AggregateBalance; - public IBindable AggregateFrequency => adjustments.AggregateFrequency; + public IBindable AggregateFrequency => sampleContainer.AggregateFrequency; - public IBindable AggregateTempo => adjustments.AggregateTempo; + public IBindable AggregateTempo => sampleContainer.AggregateTempo; } } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 67d13118d9..8dc3337525 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -41,14 +41,13 @@ namespace osu.Game.Skinning public SkinnableSound() { + InternalChild = SamplesContainer = new AudioContainer(); } public SkinnableSound([NotNull] IEnumerable samples) : this() { this.samples = samples.ToArray(); - - InternalChild = SamplesContainer = new AudioContainer(); } public SkinnableSound([NotNull] ISampleInfo sample) @@ -123,6 +122,7 @@ namespace osu.Game.Skinning { var sample = pooledProvider?.GetPooledSample(s) ?? new PoolableSkinnableSample(s); sample.Looping = Looping; + sample.Volume.Value = s.Volume / 100.0; SamplesContainer.Add(sample); } From 7180bfe4ba904507fd944d42bad39296a559dabc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Nov 2020 21:21:57 +0900 Subject: [PATCH 085/394] Unlimit number of samples per pool --- osu.Game/Rulesets/UI/Playfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 6e89f20246..460251e595 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -348,7 +348,7 @@ namespace osu.Game.Rulesets.UI public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo) { if (!samplePools.TryGetValue(sampleInfo, out var existingPool)) - AddInternal(samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 1, 30)); + AddInternal(samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 1)); return existingPool.Get(); } From d388c44428c74f2ce32cb48b0a83f900497f46b7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Nov 2020 22:30:21 +0900 Subject: [PATCH 086/394] Cleanup, refactoring, and restart sample on skin change --- osu.Game/Skinning/PoolableSkinnableSample.cs | 72 ++++++++++++++------ osu.Game/Skinning/SkinnableSound.cs | 7 -- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index ad799dd32e..e38b309efb 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -13,27 +14,48 @@ using osu.Game.Audio; namespace osu.Game.Skinning { + /// + /// A sample corresponding to an that supports being pooled and responding to skin changes. + /// public class PoolableSkinnableSample : SkinReloadableDrawable, IAggregateAudioAdjustment, IAdjustableAudioComponent { - private readonly AudioContainer sampleContainer; + /// + /// The currently-loaded . + /// + [CanBeNull] + public DrawableSample Sample { get; private set; } + private readonly AudioContainer sampleContainer; private ISampleInfo sampleInfo; - private DrawableSample sample; [Resolved] private ISampleStore sampleStore { get; set; } + /// + /// Creates a new with no applied . + /// An can be applied later via . + /// public PoolableSkinnableSample() { InternalChild = sampleContainer = new AudioContainer { RelativeSizeAxes = Axes.Both }; } + /// + /// Creates a new with an applied . + /// + /// The to attach. public PoolableSkinnableSample(ISampleInfo sampleInfo) : this() { Apply(sampleInfo); } + /// + /// Applies an that describes the sample to retrieve. + /// Only one can ever be applied to a . + /// + /// The to apply. + /// If an has already been applied to this . public void Apply(ISampleInfo sampleInfo) { if (this.sampleInfo != null) @@ -55,6 +77,11 @@ namespace osu.Game.Skinning private void updateSample() { + if (sampleInfo == null) + return; + + bool wasPlaying = Playing; + sampleContainer.Clear(); var ch = CurrentSkin.GetSample(sampleInfo); @@ -71,17 +98,34 @@ namespace osu.Game.Skinning if (ch == null) return; - sampleContainer.Add(sample = new DrawableSample(ch) { Looping = Looping }); + sampleContainer.Add(Sample = new DrawableSample(ch) { Looping = Looping }); + + // Start playback internally for the new sample if the previous one was playing beforehand. + if (wasPlaying) + Play(); } - public void Play(bool restart = true) => sample?.Play(restart); + /// + /// Plays the sample. + /// + /// Whether to play the sample from the beginning. + public void Play(bool restart = true) => Sample?.Play(restart); - public void Stop() => sample?.Stop(); + /// + /// Stops the sample. + /// + public void Stop() => Sample?.Stop(); - public bool Playing => sample?.Playing ?? false; + /// + /// Whether the sample is currently playing. + /// + public bool Playing => Sample?.Playing ?? false; private bool looping; + /// + /// Gets or sets whether the sample should loop on completion. + /// public bool Looping { get => looping; @@ -89,29 +133,17 @@ namespace osu.Game.Skinning { looping = value; - if (sample != null) - sample.Looping = value; + if (Sample != null) + Sample.Looping = value; } } - /// - /// The volume of this component. - /// public BindableNumber Volume => sampleContainer.Volume; - /// - /// The playback balance of this sample (-1 .. 1 where 0 is centered) - /// public BindableNumber Balance => sampleContainer.Balance; - /// - /// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency. - /// public BindableNumber Frequency => sampleContainer.Frequency; - /// - /// Rate at which the component is played back (does not affect pitch). 1 is 100% playback speed. - /// public BindableNumber Tempo => sampleContainer.Tempo; public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable); diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 8dc3337525..0f39784138 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -103,13 +103,6 @@ namespace osu.Game.Skinning SamplesContainer.ForEach(c => c.Stop()); } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - // Start playback internally for the new samples if the previous ones were playing beforehand. - if (IsPlaying) - Play(); - } - private void updateSamples() { bool wasPlaying = IsPlaying; From a53848ef9bbc09b8c4c75a044c3db6a543f85dc0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Nov 2020 22:30:41 +0900 Subject: [PATCH 087/394] Fix storyboard imlpementation --- osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index b8d212d3c9..904af730de 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -37,8 +37,8 @@ namespace osu.Game.Storyboards.Drawables foreach (var mod in mods.Value.OfType()) { - // foreach (var sample in SamplesContainer) - // mod.ApplyToSample(sample.Sample); + foreach (var sample in SamplesContainer) + mod.ApplyToSample(sample.Sample); } } From 8a656f7cee1ac51578420e9e9576c8055a8fde67 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Nov 2020 22:42:44 +0900 Subject: [PATCH 088/394] Fix missing SkinChanged event + safety --- osu.Game/Skinning/SkinnableSound.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 0f39784138..bb747a2176 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -55,7 +55,7 @@ namespace osu.Game.Skinning { } - private ISampleInfo[] samples; + private ISampleInfo[] samples = Array.Empty(); public ISampleInfo[] Samples { @@ -103,6 +103,12 @@ namespace osu.Game.Skinning SamplesContainer.ForEach(c => c.Stop()); } + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + updateSamples(); + } + private void updateSamples() { bool wasPlaying = IsPlaying; From 7c83a27002a033a114b42a6b933c998d8858f5fa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Nov 2020 22:47:11 +0900 Subject: [PATCH 089/394] Add more xmldocs --- osu.Game/Skinning/PoolableSkinnableSample.cs | 2 +- osu.Game/Skinning/SkinnableSound.cs | 29 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index e38b309efb..adc58ee94e 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -124,7 +124,7 @@ namespace osu.Game.Skinning private bool looping; /// - /// Gets or sets whether the sample should loop on completion. + /// Whether the sample should loop on completion. /// public bool Looping { diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index bb747a2176..24dddaf758 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -16,6 +16,9 @@ using osu.Game.Audio; namespace osu.Game.Skinning { + /// + /// A sound consisting of one or more samples to be played. + /// public class SkinnableSound : SkinReloadableDrawable, IAdjustableAudioComponent { public override bool RemoveWhenNotAlive => false; @@ -39,17 +42,28 @@ namespace osu.Game.Skinning [Resolved(CanBeNull = true)] private IPooledSampleProvider pooledProvider { get; set; } + /// + /// Creates a new . + /// public SkinnableSound() { InternalChild = SamplesContainer = new AudioContainer(); } + /// + /// Creates a new with some initial samples. + /// + /// The initial samples. public SkinnableSound([NotNull] IEnumerable samples) : this() { this.samples = samples.ToArray(); } + /// + /// Creates a new with an initial sample. + /// + /// The initial sample. public SkinnableSound([NotNull] ISampleInfo sample) : this(new[] { sample }) { @@ -57,6 +71,9 @@ namespace osu.Game.Skinning private ISampleInfo[] samples = Array.Empty(); + /// + /// The samples that should be played. + /// public ISampleInfo[] Samples { get => samples; @@ -76,6 +93,9 @@ namespace osu.Game.Skinning private bool looping; + /// + /// Whether the samples should loop on completion. + /// public bool Looping { get => looping; @@ -89,6 +109,9 @@ namespace osu.Game.Skinning } } + /// + /// Plays the samples. + /// public virtual void Play() { SamplesContainer.ForEach(c => @@ -98,6 +121,9 @@ namespace osu.Game.Skinning }); } + /// + /// Stops the samples. + /// public virtual void Stop() { SamplesContainer.ForEach(c => c.Stop()); @@ -149,6 +175,9 @@ namespace osu.Game.Skinning public void RemoveAllAdjustments(AdjustableProperty type) => SamplesContainer.RemoveAllAdjustments(type); + /// + /// Whether any samples currently playing. + /// public bool IsPlaying => SamplesContainer.Any(s => s.Playing); #endregion From d467a00eeac6f04e6b192c7513538b22e3d1f0d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 00:11:31 +0900 Subject: [PATCH 090/394] Add initial followpoint pooling implementation --- .../TestSceneFollowPoints.cs | 552 +++++++++--------- .../Drawables/Connections/FollowPoint.cs | 5 +- .../Connections/FollowPointConnection.cs | 134 +---- .../Connections/FollowPointRenderer.cs | 167 ++++-- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 +- 5 files changed, 423 insertions(+), 439 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 6c077eb214..3ebb747a21 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -1,276 +1,276 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; -using osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; -using osu.Game.Tests.Visual; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Tests -{ - public class TestSceneFollowPoints : OsuTestScene - { - private Container hitObjectContainer; - private FollowPointRenderer followPointRenderer; - - [SetUp] - public void Setup() => Schedule(() => - { - Children = new Drawable[] - { - hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both }, - followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } - }; - }); - - [Test] - public void TestAddObject() - { - addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); - - assertGroups(); - } - - [Test] - public void TestRemoveObject() - { - addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); - - removeObjectStep(() => getObject(0)); - - assertGroups(); - } - - [Test] - public void TestAddMultipleObjects() - { - addMultipleObjectsStep(); - - assertGroups(); - } - - [Test] - public void TestRemoveEndObject() - { - addMultipleObjectsStep(); - - removeObjectStep(() => getObject(4)); - - assertGroups(); - } - - [Test] - public void TestRemoveStartObject() - { - addMultipleObjectsStep(); - - removeObjectStep(() => getObject(0)); - - assertGroups(); - } - - [Test] - public void TestRemoveMiddleObject() - { - addMultipleObjectsStep(); - - removeObjectStep(() => getObject(2)); - - assertGroups(); - } - - [Test] - public void TestMoveObject() - { - addMultipleObjectsStep(); - - AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100)); - - assertGroups(); - } - - [TestCase(0, 0)] // Start -> Start - [TestCase(0, 2)] // Start -> Middle - [TestCase(0, 5)] // Start -> End - [TestCase(2, 0)] // Middle -> Start - [TestCase(1, 3)] // Middle -> Middle (forwards) - [TestCase(3, 1)] // Middle -> Middle (backwards) - [TestCase(4, 0)] // End -> Start - [TestCase(4, 2)] // End -> Middle - [TestCase(4, 4)] // End -> End - public void TestReorderObjects(int startIndex, int endIndex) - { - addMultipleObjectsStep(); - - reorderObjectStep(startIndex, endIndex); - - assertGroups(); - } - - [Test] - public void TestStackedObjects() - { - addObjectsStep(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(300, 100) }, - new HitCircle - { - Position = new Vector2(300, 300), - StackHeight = 20 - }, - }); - - assertDirections(); - } - - private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }); - - private void addObjectsStep(Func ctorFunc) - { - AddStep("add hitobjects", () => - { - var objects = ctorFunc(); - - for (int i = 0; i < objects.Length; i++) - { - objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1); - objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - DrawableOsuHitObject drawableObject = null; - - switch (objects[i]) - { - case HitCircle circle: - drawableObject = new DrawableHitCircle(circle); - break; - - case Slider slider: - drawableObject = new DrawableSlider(slider); - break; - - case Spinner spinner: - drawableObject = new DrawableSpinner(spinner); - break; - } - - hitObjectContainer.Add(drawableObject); - followPointRenderer.AddFollowPoints(objects[i]); - } - }); - } - - private void removeObjectStep(Func getFunc) - { - AddStep("remove hitobject", () => - { - var drawableObject = getFunc.Invoke(); - - hitObjectContainer.Remove(drawableObject); - followPointRenderer.RemoveFollowPoints(drawableObject.HitObject); - }); - } - - private void reorderObjectStep(int startIndex, int endIndex) - { - AddStep($"move object {startIndex} to {endIndex}", () => - { - DrawableOsuHitObject toReorder = getObject(startIndex); - - double targetTime; - if (endIndex < hitObjectContainer.Count) - targetTime = getObject(endIndex).HitObject.StartTime - 1; - else - targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1; - - hitObjectContainer.Remove(toReorder); - toReorder.HitObject.StartTime = targetTime; - hitObjectContainer.Add(toReorder); - }); - } - - private void assertGroups() - { - AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count); - AddAssert("group endpoints are correct", () => - { - for (int i = 0; i < hitObjectContainer.Count; i++) - { - DrawableOsuHitObject expectedStart = getObject(i); - DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; - - if (getGroup(i).Start != expectedStart.HitObject) - throw new AssertionException($"Object {i} expected to be the start of group {i}."); - - if (getGroup(i).End != expectedEnd?.HitObject) - throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}."); - } - - return true; - }); - } - - private void assertDirections() - { - AddAssert("group directions are correct", () => - { - for (int i = 0; i < hitObjectContainer.Count; i++) - { - DrawableOsuHitObject expectedStart = getObject(i); - DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; - - if (expectedEnd == null) - continue; - - var points = getGroup(i).ChildrenOfType().ToArray(); - if (points.Length == 0) - continue; - - float expectedDirection = MathF.Atan2(expectedStart.Position.Y - expectedEnd.Position.Y, expectedStart.Position.X - expectedEnd.Position.X); - float realDirection = MathF.Atan2(expectedStart.Position.Y - points[^1].Position.Y, expectedStart.Position.X - points[^1].Position.X); - - if (!Precision.AlmostEquals(expectedDirection, realDirection)) - throw new AssertionException($"Expected group {i} in direction {expectedDirection}, but was {realDirection}."); - } - - return true; - }); - } - - private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; - - private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index]; - - private class TestHitObjectContainer : Container - { - protected override int Compare(Drawable x, Drawable y) - { - var osuX = (DrawableOsuHitObject)x; - var osuY = (DrawableOsuHitObject)y; - - int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime); - - if (compare == 0) - return base.Compare(x, y); - - return compare; - } - } - } -} +// // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// // See the LICENCE file in the repository root for full licence text. +// +// using System; +// using System.Linq; +// using NUnit.Framework; +// using osu.Framework.Graphics; +// using osu.Framework.Graphics.Containers; +// using osu.Framework.Testing; +// using osu.Framework.Utils; +// using osu.Game.Beatmaps; +// using osu.Game.Beatmaps.ControlPoints; +// using osu.Game.Rulesets.Osu.Objects; +// using osu.Game.Rulesets.Osu.Objects.Drawables; +// using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; +// using osu.Game.Tests.Visual; +// using osuTK; +// +// namespace osu.Game.Rulesets.Osu.Tests +// { +// public class TestSceneFollowPoints : OsuTestScene +// { +// private Container hitObjectContainer; +// private FollowPointRenderer followPointRenderer; +// +// [SetUp] +// public void Setup() => Schedule(() => +// { +// Children = new Drawable[] +// { +// hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both }, +// followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } +// }; +// }); +// +// [Test] +// public void TestAddObject() +// { +// addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); +// +// assertGroups(); +// } +// +// [Test] +// public void TestRemoveObject() +// { +// addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); +// +// removeObjectStep(() => getObject(0)); +// +// assertGroups(); +// } +// +// [Test] +// public void TestAddMultipleObjects() +// { +// addMultipleObjectsStep(); +// +// assertGroups(); +// } +// +// [Test] +// public void TestRemoveEndObject() +// { +// addMultipleObjectsStep(); +// +// removeObjectStep(() => getObject(4)); +// +// assertGroups(); +// } +// +// [Test] +// public void TestRemoveStartObject() +// { +// addMultipleObjectsStep(); +// +// removeObjectStep(() => getObject(0)); +// +// assertGroups(); +// } +// +// [Test] +// public void TestRemoveMiddleObject() +// { +// addMultipleObjectsStep(); +// +// removeObjectStep(() => getObject(2)); +// +// assertGroups(); +// } +// +// [Test] +// public void TestMoveObject() +// { +// addMultipleObjectsStep(); +// +// AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100)); +// +// assertGroups(); +// } +// +// [TestCase(0, 0)] // Start -> Start +// [TestCase(0, 2)] // Start -> Middle +// [TestCase(0, 5)] // Start -> End +// [TestCase(2, 0)] // Middle -> Start +// [TestCase(1, 3)] // Middle -> Middle (forwards) +// [TestCase(3, 1)] // Middle -> Middle (backwards) +// [TestCase(4, 0)] // End -> Start +// [TestCase(4, 2)] // End -> Middle +// [TestCase(4, 4)] // End -> End +// public void TestReorderObjects(int startIndex, int endIndex) +// { +// addMultipleObjectsStep(); +// +// reorderObjectStep(startIndex, endIndex); +// +// assertGroups(); +// } +// +// [Test] +// public void TestStackedObjects() +// { +// addObjectsStep(() => new OsuHitObject[] +// { +// new HitCircle { Position = new Vector2(300, 100) }, +// new HitCircle +// { +// Position = new Vector2(300, 300), +// StackHeight = 20 +// }, +// }); +// +// assertDirections(); +// } +// +// private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[] +// { +// new HitCircle { Position = new Vector2(100, 100) }, +// new HitCircle { Position = new Vector2(200, 200) }, +// new HitCircle { Position = new Vector2(300, 300) }, +// new HitCircle { Position = new Vector2(400, 400) }, +// new HitCircle { Position = new Vector2(500, 500) }, +// }); +// +// private void addObjectsStep(Func ctorFunc) +// { +// AddStep("add hitobjects", () => +// { +// var objects = ctorFunc(); +// +// for (int i = 0; i < objects.Length; i++) +// { +// objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1); +// objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); +// +// DrawableOsuHitObject drawableObject = null; +// +// switch (objects[i]) +// { +// case HitCircle circle: +// drawableObject = new DrawableHitCircle(circle); +// break; +// +// case Slider slider: +// drawableObject = new DrawableSlider(slider); +// break; +// +// case Spinner spinner: +// drawableObject = new DrawableSpinner(spinner); +// break; +// } +// +// hitObjectContainer.Add(drawableObject); +// followPointRenderer.AddFollowPoints(objects[i]); +// } +// }); +// } +// +// private void removeObjectStep(Func getFunc) +// { +// AddStep("remove hitobject", () => +// { +// var drawableObject = getFunc.Invoke(); +// +// hitObjectContainer.Remove(drawableObject); +// followPointRenderer.RemoveFollowPoints(drawableObject.HitObject); +// }); +// } +// +// private void reorderObjectStep(int startIndex, int endIndex) +// { +// AddStep($"move object {startIndex} to {endIndex}", () => +// { +// DrawableOsuHitObject toReorder = getObject(startIndex); +// +// double targetTime; +// if (endIndex < hitObjectContainer.Count) +// targetTime = getObject(endIndex).HitObject.StartTime - 1; +// else +// targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1; +// +// hitObjectContainer.Remove(toReorder); +// toReorder.HitObject.StartTime = targetTime; +// hitObjectContainer.Add(toReorder); +// }); +// } +// +// private void assertGroups() +// { +// AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count); +// AddAssert("group endpoints are correct", () => +// { +// for (int i = 0; i < hitObjectContainer.Count; i++) +// { +// DrawableOsuHitObject expectedStart = getObject(i); +// DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; +// +// if (getGroup(i).Start != expectedStart.HitObject) +// throw new AssertionException($"Object {i} expected to be the start of group {i}."); +// +// if (getGroup(i).End != expectedEnd?.HitObject) +// throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}."); +// } +// +// return true; +// }); +// } +// +// private void assertDirections() +// { +// AddAssert("group directions are correct", () => +// { +// for (int i = 0; i < hitObjectContainer.Count; i++) +// { +// DrawableOsuHitObject expectedStart = getObject(i); +// DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; +// +// if (expectedEnd == null) +// continue; +// +// var points = getGroup(i).ChildrenOfType().ToArray(); +// if (points.Length == 0) +// continue; +// +// float expectedDirection = MathF.Atan2(expectedStart.Position.Y - expectedEnd.Position.Y, expectedStart.Position.X - expectedEnd.Position.X); +// float realDirection = MathF.Atan2(expectedStart.Position.Y - points[^1].Position.Y, expectedStart.Position.X - points[^1].Position.X); +// +// if (!Precision.AlmostEquals(expectedDirection, realDirection)) +// throw new AssertionException($"Expected group {i} in direction {expectedDirection}, but was {realDirection}."); +// } +// +// return true; +// }); +// } +// +// private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; +// +// private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index]; +// +// private class TestHitObjectContainer : Container +// { +// protected override int Compare(Drawable x, Drawable y) +// { +// var osuX = (DrawableOsuHitObject)x; +// var osuY = (DrawableOsuHitObject)y; +// +// int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime); +// +// if (compare == 0) +// return base.Compare(x, y); +// +// return compare; +// } +// } +// } +// } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index a981648444..3e2ab65bb2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Game.Skinning; @@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// A single follow point positioned between two adjacent s. /// - public class FollowPoint : Container, IAnimationTimeReference + public class FollowPoint : PoolableDrawable, IAnimationTimeReference { private const float width = 8; @@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { Origin = Anchor.Centre; - Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new CircularContainer + InternalChild = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new CircularContainer { Masking = true, AutoSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 3a9e19b361..1d82e91c0e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -2,11 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; -using JetBrains.Annotations; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects; using osuTK; @@ -15,150 +12,77 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// Visualises the s between two s. /// - public class FollowPointConnection : CompositeDrawable + public class FollowPointConnection : PoolableDrawable { // Todo: These shouldn't be constants - private const int spacing = 32; - private const double preempt = 800; + public const int SPACING = 32; + public const double PREEMPT = 800; - public override bool RemoveWhenNotAlive => false; + public FollowPointRenderer.FollowPointLifetimeEntry Entry; + public DrawablePool Pool; - /// - /// The start time of . - /// - public readonly Bindable StartTime = new BindableDouble(); - - /// - /// The which s will exit from. - /// - [NotNull] - public readonly OsuHitObject Start; - - /// - /// Creates a new . - /// - /// The which s will exit from. - public FollowPointConnection([NotNull] OsuHitObject start) + protected override void FreeAfterUse() { - Start = start; - - RelativeSizeAxes = Axes.Both; - - StartTime.BindTo(start.StartTimeBindable); + base.FreeAfterUse(); + ClearInternal(false); } - protected override void LoadComplete() + protected override void PrepareForUse() { - base.LoadComplete(); - bindEvents(Start); - } + base.PrepareForUse(); - private OsuHitObject end; + OsuHitObject start = Entry.Start; + OsuHitObject end = Entry.End; - /// - /// The which s will enter. - /// - [CanBeNull] - public OsuHitObject End - { - get => end; - set - { - end = value; + double startTime = start.GetEndTime(); - if (end != null) - bindEvents(end); - - if (IsLoaded) - scheduleRefresh(); - else - refresh(); - } - } - - private void bindEvents(OsuHitObject obj) - { - obj.PositionBindable.BindValueChanged(_ => scheduleRefresh()); - obj.DefaultsApplied += _ => scheduleRefresh(); - } - - private void scheduleRefresh() - { - Scheduler.AddOnce(refresh); - } - - private void refresh() - { - double startTime = Start.GetEndTime(); - - LifetimeStart = startTime; - - if (End == null || End.NewCombo || Start is Spinner || End is Spinner) - { - // ensure we always set a lifetime for full LifetimeManagementContainer benefits - LifetimeEnd = LifetimeStart; + if (end == null || end.NewCombo || start is Spinner || end is Spinner) return; - } - Vector2 startPosition = Start.StackedEndPosition; - Vector2 endPosition = End.StackedPosition; - double endTime = End.StartTime; + Vector2 startPosition = start.StackedEndPosition; + Vector2 endPosition = end.StackedPosition; + double endTime = end.StartTime; Vector2 distanceVector = endPosition - startPosition; int distance = (int)distanceVector.Length; float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); double duration = endTime - startTime; - double? firstTransformStartTime = null; double finalTransformEndTime = startTime; - int point = 0; - - ClearInternal(); - - for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) + for (int d = (int)(SPACING * 1.5); d < distance - SPACING; d += SPACING) { float fraction = (float)d / distance; Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; Vector2 pointEndPosition = startPosition + fraction * distanceVector; double fadeOutTime = startTime + fraction * duration; - double fadeInTime = fadeOutTime - preempt; + double fadeInTime = fadeOutTime - PREEMPT; FollowPoint fp; - AddInternal(fp = new FollowPoint()); - - Debug.Assert(End != null); + AddInternal(fp = Pool.Get()); + fp.ClearTransforms(); fp.Position = pointStartPosition; fp.Rotation = rotation; fp.Alpha = 0; - fp.Scale = new Vector2(1.5f * End.Scale); - - firstTransformStartTime ??= fadeInTime; + fp.Scale = new Vector2(1.5f * end.Scale); fp.AnimationStartTime = fadeInTime; using (fp.BeginAbsoluteSequence(fadeInTime)) { - fp.FadeIn(End.TimeFadeIn); - fp.ScaleTo(End.Scale, End.TimeFadeIn, Easing.Out); - fp.MoveTo(pointEndPosition, End.TimeFadeIn, Easing.Out); - fp.Delay(fadeOutTime - fadeInTime).FadeOut(End.TimeFadeIn); + fp.FadeIn(end.TimeFadeIn); + fp.ScaleTo(end.Scale, end.TimeFadeIn, Easing.Out); + fp.MoveTo(pointEndPosition, end.TimeFadeIn, Easing.Out); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn); - finalTransformEndTime = fadeOutTime + End.TimeFadeIn; + finalTransformEndTime = fadeOutTime + end.TimeFadeIn; } - - point++; } - int excessPoints = InternalChildren.Count - point; - for (int i = 0; i < excessPoints; i++) - RemoveInternal(InternalChildren[^1]); - // todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed. - LifetimeStart = firstTransformStartTime ?? startTime; - LifetimeEnd = finalTransformEndTime; + Entry.LifetimeEnd = finalTransformEndTime; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index be1392d7c3..ac7b78a25b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -2,53 +2,57 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; +using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Objects; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { /// /// Visualises connections between s. /// - public class FollowPointRenderer : LifetimeManagementContainer + public class FollowPointRenderer : CompositeDrawable { - /// - /// All the s contained by this . - /// - internal IReadOnlyList Connections => connections; - - private readonly List connections = new List(); - public override bool RemoveCompletedTransforms => false; - /// - /// Adds the s around an . - /// This includes s leading into , and s exiting . - /// - /// The to add s for. - public void AddFollowPoints(OsuHitObject hitObject) - => addConnection(new FollowPointConnection(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g)))); + private DrawablePool connectionPool; + private DrawablePool pointPool; - /// - /// Removes the s around an . - /// This includes s leading into , and s exiting . - /// - /// The to remove s for. - public void RemoveFollowPoints(OsuHitObject hitObject) => removeGroup(connections.Single(g => g.Start == hitObject)); + private readonly List lifetimeEntries = new List(); + private readonly Dictionary connectionsInUse = new Dictionary(); + private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); - /// - /// Adds a to this . - /// - /// The to add. - /// The index of in . - private void addConnection(FollowPointConnection connection) + public FollowPointRenderer() { - // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections - int index = connections.AddInPlace(connection, Comparer.Create((g1, g2) => + lifetimeManager.EntryBecameAlive += onEntryBecameAlive; + lifetimeManager.EntryBecameDead += onEntryBecameDead; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] { - int comp = g1.StartTime.Value.CompareTo(g2.StartTime.Value); + connectionPool = new DrawablePool(1, 200), + pointPool = new DrawablePool(50, 1000) + }; + + MakeChildAlive(connectionPool); + MakeChildAlive(pointPool); + } + + public void AddFollowPoints2(OsuHitObject hitObject) + { + var newEntry = new FollowPointLifetimeEntry(hitObject); + + var index = lifetimeEntries.AddInPlace(newEntry, Comparer.Create((e1, e2) => + { + int comp = e1.Start.StartTime.CompareTo(e2.Start.StartTime); if (comp != 0) return comp; @@ -61,19 +65,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections return -1; })); - if (index < connections.Count - 1) + if (index < lifetimeEntries.Count - 1) { // Update the connection's end point to the next connection's start point // h1 -> -> -> h2 // connection nextGroup - FollowPointConnection nextConnection = connections[index + 1]; - connection.End = nextConnection.Start; + FollowPointLifetimeEntry nextEntry = lifetimeEntries[index + 1]; + newEntry.End = nextEntry.Start; } else { // The end point may be non-null during re-ordering - connection.End = null; + newEntry.End = null; } if (index > 0) @@ -82,23 +86,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections // h1 -> -> -> h2 // prevGroup connection - FollowPointConnection previousConnection = connections[index - 1]; - previousConnection.End = connection.Start; + FollowPointLifetimeEntry previousEntry = lifetimeEntries[index - 1]; + previousEntry.End = newEntry.Start; } - AddInternal(connection); + lifetimeManager.AddEntry(newEntry); } - /// - /// Removes a from this . - /// - /// The to remove. - /// Whether was removed. - private void removeGroup(FollowPointConnection connection) + public void RemoveFollowPoints2(OsuHitObject hitObject) { - RemoveInternal(connection); + int index = lifetimeEntries.FindIndex(e => e.Start == hitObject); + var entry = lifetimeEntries[index]; - int index = connections.IndexOf(connection); + lifetimeEntries.RemoveAt(index); + lifetimeManager.RemoveEntry(entry); if (index > 0) { @@ -106,18 +107,76 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections // h1 -> -> -> h2 -> -> -> h3 // prevGroup connection nextGroup // The current connection's end point is used since there may not be a next connection - FollowPointConnection previousConnection = connections[index - 1]; - previousConnection.End = connection.End; + FollowPointLifetimeEntry previousEntry = lifetimeEntries[index - 1]; + previousEntry.End = entry.End; } - - connections.Remove(connection); } - private void onStartTimeChanged(FollowPointConnection connection) + protected override bool CheckChildrenLife() => lifetimeManager.Update(Time.Current); + + private void onEntryBecameAlive(LifetimeEntry entry) { - // Naive but can be improved if performance becomes an issue - removeGroup(connection); - addConnection(connection); + var connection = connectionPool.Get(c => + { + c.Entry = (FollowPointLifetimeEntry)entry; + c.Pool = pointPool; + }); + + connectionsInUse[entry] = connection; + + AddInternal(connection); + MakeChildAlive(connection); + } + + private void onEntryBecameDead(LifetimeEntry entry) + { + RemoveInternal(connectionsInUse[entry]); + connectionsInUse.Remove(entry); + } + + public class FollowPointLifetimeEntry : LifetimeEntry + { + public readonly OsuHitObject Start; + + public FollowPointLifetimeEntry(OsuHitObject start) + { + Start = start; + + LifetimeStart = LifetimeEnd = Start.StartTime; + } + + private OsuHitObject end; + + public OsuHitObject End + { + get => end; + set + { + end = value; + computeLifetimes(); + } + } + + private void computeLifetimes() + { + if (end == null) + { + LifetimeEnd = LifetimeStart; + return; + } + + Vector2 startPosition = Start.StackedEndPosition; + Vector2 endPosition = End.StackedPosition; + Vector2 distanceVector = endPosition - startPosition; + float fraction = (int)(FollowPointConnection.SPACING * 1.5) / distanceVector.Length; + + double duration = End.StartTime - Start.GetEndTime(); + + double fadeOutTime = Start.StartTime + fraction * duration; + double fadeInTime = fadeOutTime - FollowPointConnection.PREEMPT; + + LifetimeStart = fadeInTime; + } } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index c816502d61..a8d9423bf6 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -125,13 +125,13 @@ namespace osu.Game.Rulesets.Osu.UI protected override void OnHitObjectAdded(HitObject hitObject) { base.OnHitObjectAdded(hitObject); - followPoints.AddFollowPoints((OsuHitObject)hitObject); + followPoints.AddFollowPoints2((OsuHitObject)hitObject); } protected override void OnHitObjectRemoved(HitObject hitObject) { base.OnHitObjectRemoved(hitObject); - followPoints.RemoveFollowPoints((OsuHitObject)hitObject); + followPoints.RemoveFollowPoints2((OsuHitObject)hitObject); } public void OnHitObjectLoaded(Drawable drawable) From 6356b2dde93c72b8fe4ee891b7cc649f3cca96ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 19 Nov 2020 22:11:52 +0100 Subject: [PATCH 091/394] Prevent editor from crashing for rulesets with no compose screen implementation --- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index d9948aa23c..46d5eb40b4 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -32,7 +32,8 @@ namespace osu.Game.Screens.Edit.Compose composer = ruleset?.CreateHitObjectComposer(); // make the composer available to the timeline and other components in this screen. - dependencies.CacheAs(composer); + if (composer != null) + dependencies.CacheAs(composer); return dependencies; } From 1df3f88fc44275ee1ae1bfe3404259557c5d6b60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 12:32:17 +0900 Subject: [PATCH 092/394] 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 4657896fac..6dab6edc5e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 704ac5a611..54f3fcede6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 346bd892b0..692dac909a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - + From b547abafb2f4bd702b7448d7556485ba76e536b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 13:46:21 +0900 Subject: [PATCH 093/394] Fix slider right click context menus not being shown outside the valid playfield area Closes #10816. --- .../Sliders/Components/PathControlPointVisualiser.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 7375c0e981..ce5dc4855e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -20,12 +20,15 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; +using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield. + internal readonly Container Pieces; internal readonly Container Connections; From a3145ed96dfbd45ff58fdd744e3490528ce00946 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 13:54:41 +0900 Subject: [PATCH 094/394] Fix test compile errors --- .../TestSceneFollowPoints.cs | 561 +++++++++--------- .../Connections/FollowPointRenderer.cs | 2 + 2 files changed, 287 insertions(+), 276 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 3ebb747a21..ef6275a7e7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -1,276 +1,285 @@ -// // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// // See the LICENCE file in the repository root for full licence text. -// -// using System; -// using System.Linq; -// using NUnit.Framework; -// using osu.Framework.Graphics; -// using osu.Framework.Graphics.Containers; -// using osu.Framework.Testing; -// using osu.Framework.Utils; -// using osu.Game.Beatmaps; -// using osu.Game.Beatmaps.ControlPoints; -// using osu.Game.Rulesets.Osu.Objects; -// using osu.Game.Rulesets.Osu.Objects.Drawables; -// using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; -// using osu.Game.Tests.Visual; -// using osuTK; -// -// namespace osu.Game.Rulesets.Osu.Tests -// { -// public class TestSceneFollowPoints : OsuTestScene -// { -// private Container hitObjectContainer; -// private FollowPointRenderer followPointRenderer; -// -// [SetUp] -// public void Setup() => Schedule(() => -// { -// Children = new Drawable[] -// { -// hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both }, -// followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } -// }; -// }); -// -// [Test] -// public void TestAddObject() -// { -// addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); -// -// assertGroups(); -// } -// -// [Test] -// public void TestRemoveObject() -// { -// addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); -// -// removeObjectStep(() => getObject(0)); -// -// assertGroups(); -// } -// -// [Test] -// public void TestAddMultipleObjects() -// { -// addMultipleObjectsStep(); -// -// assertGroups(); -// } -// -// [Test] -// public void TestRemoveEndObject() -// { -// addMultipleObjectsStep(); -// -// removeObjectStep(() => getObject(4)); -// -// assertGroups(); -// } -// -// [Test] -// public void TestRemoveStartObject() -// { -// addMultipleObjectsStep(); -// -// removeObjectStep(() => getObject(0)); -// -// assertGroups(); -// } -// -// [Test] -// public void TestRemoveMiddleObject() -// { -// addMultipleObjectsStep(); -// -// removeObjectStep(() => getObject(2)); -// -// assertGroups(); -// } -// -// [Test] -// public void TestMoveObject() -// { -// addMultipleObjectsStep(); -// -// AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100)); -// -// assertGroups(); -// } -// -// [TestCase(0, 0)] // Start -> Start -// [TestCase(0, 2)] // Start -> Middle -// [TestCase(0, 5)] // Start -> End -// [TestCase(2, 0)] // Middle -> Start -// [TestCase(1, 3)] // Middle -> Middle (forwards) -// [TestCase(3, 1)] // Middle -> Middle (backwards) -// [TestCase(4, 0)] // End -> Start -// [TestCase(4, 2)] // End -> Middle -// [TestCase(4, 4)] // End -> End -// public void TestReorderObjects(int startIndex, int endIndex) -// { -// addMultipleObjectsStep(); -// -// reorderObjectStep(startIndex, endIndex); -// -// assertGroups(); -// } -// -// [Test] -// public void TestStackedObjects() -// { -// addObjectsStep(() => new OsuHitObject[] -// { -// new HitCircle { Position = new Vector2(300, 100) }, -// new HitCircle -// { -// Position = new Vector2(300, 300), -// StackHeight = 20 -// }, -// }); -// -// assertDirections(); -// } -// -// private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[] -// { -// new HitCircle { Position = new Vector2(100, 100) }, -// new HitCircle { Position = new Vector2(200, 200) }, -// new HitCircle { Position = new Vector2(300, 300) }, -// new HitCircle { Position = new Vector2(400, 400) }, -// new HitCircle { Position = new Vector2(500, 500) }, -// }); -// -// private void addObjectsStep(Func ctorFunc) -// { -// AddStep("add hitobjects", () => -// { -// var objects = ctorFunc(); -// -// for (int i = 0; i < objects.Length; i++) -// { -// objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1); -// objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); -// -// DrawableOsuHitObject drawableObject = null; -// -// switch (objects[i]) -// { -// case HitCircle circle: -// drawableObject = new DrawableHitCircle(circle); -// break; -// -// case Slider slider: -// drawableObject = new DrawableSlider(slider); -// break; -// -// case Spinner spinner: -// drawableObject = new DrawableSpinner(spinner); -// break; -// } -// -// hitObjectContainer.Add(drawableObject); -// followPointRenderer.AddFollowPoints(objects[i]); -// } -// }); -// } -// -// private void removeObjectStep(Func getFunc) -// { -// AddStep("remove hitobject", () => -// { -// var drawableObject = getFunc.Invoke(); -// -// hitObjectContainer.Remove(drawableObject); -// followPointRenderer.RemoveFollowPoints(drawableObject.HitObject); -// }); -// } -// -// private void reorderObjectStep(int startIndex, int endIndex) -// { -// AddStep($"move object {startIndex} to {endIndex}", () => -// { -// DrawableOsuHitObject toReorder = getObject(startIndex); -// -// double targetTime; -// if (endIndex < hitObjectContainer.Count) -// targetTime = getObject(endIndex).HitObject.StartTime - 1; -// else -// targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1; -// -// hitObjectContainer.Remove(toReorder); -// toReorder.HitObject.StartTime = targetTime; -// hitObjectContainer.Add(toReorder); -// }); -// } -// -// private void assertGroups() -// { -// AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count); -// AddAssert("group endpoints are correct", () => -// { -// for (int i = 0; i < hitObjectContainer.Count; i++) -// { -// DrawableOsuHitObject expectedStart = getObject(i); -// DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; -// -// if (getGroup(i).Start != expectedStart.HitObject) -// throw new AssertionException($"Object {i} expected to be the start of group {i}."); -// -// if (getGroup(i).End != expectedEnd?.HitObject) -// throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}."); -// } -// -// return true; -// }); -// } -// -// private void assertDirections() -// { -// AddAssert("group directions are correct", () => -// { -// for (int i = 0; i < hitObjectContainer.Count; i++) -// { -// DrawableOsuHitObject expectedStart = getObject(i); -// DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; -// -// if (expectedEnd == null) -// continue; -// -// var points = getGroup(i).ChildrenOfType().ToArray(); -// if (points.Length == 0) -// continue; -// -// float expectedDirection = MathF.Atan2(expectedStart.Position.Y - expectedEnd.Position.Y, expectedStart.Position.X - expectedEnd.Position.X); -// float realDirection = MathF.Atan2(expectedStart.Position.Y - points[^1].Position.Y, expectedStart.Position.X - points[^1].Position.X); -// -// if (!Precision.AlmostEquals(expectedDirection, realDirection)) -// throw new AssertionException($"Expected group {i} in direction {expectedDirection}, but was {realDirection}."); -// } -// -// return true; -// }); -// } -// -// private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; -// -// private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index]; -// -// private class TestHitObjectContainer : Container -// { -// protected override int Compare(Drawable x, Drawable y) -// { -// var osuX = (DrawableOsuHitObject)x; -// var osuY = (DrawableOsuHitObject)y; -// -// int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime); -// -// if (compare == 0) -// return base.Compare(x, y); -// -// return compare; -// } -// } -// } -// } +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneFollowPoints : OsuTestScene + { + private Container hitObjectContainer; + private FollowPointRenderer followPointRenderer; + + [SetUp] + public void Setup() => Schedule(() => + { + Children = new Drawable[] + { + hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both }, + followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } + }; + }); + + [Test] + public void TestAddObject() + { + addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); + + assertGroups(); + } + + [Test] + public void TestRemoveObject() + { + addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); + + removeObjectStep(() => getObject(0)); + + assertGroups(); + } + + [Test] + public void TestAddMultipleObjects() + { + addMultipleObjectsStep(); + + assertGroups(); + } + + [Test] + public void TestRemoveEndObject() + { + addMultipleObjectsStep(); + + removeObjectStep(() => getObject(4)); + + assertGroups(); + } + + [Test] + public void TestRemoveStartObject() + { + addMultipleObjectsStep(); + + removeObjectStep(() => getObject(0)); + + assertGroups(); + } + + [Test] + public void TestRemoveMiddleObject() + { + addMultipleObjectsStep(); + + removeObjectStep(() => getObject(2)); + + assertGroups(); + } + + [Test] + public void TestMoveObject() + { + addMultipleObjectsStep(); + + AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100)); + + assertGroups(); + } + + [TestCase(0, 0)] // Start -> Start + [TestCase(0, 2)] // Start -> Middle + [TestCase(0, 5)] // Start -> End + [TestCase(2, 0)] // Middle -> Start + [TestCase(1, 3)] // Middle -> Middle (forwards) + [TestCase(3, 1)] // Middle -> Middle (backwards) + [TestCase(4, 0)] // End -> Start + [TestCase(4, 2)] // End -> Middle + [TestCase(4, 4)] // End -> End + public void TestReorderObjects(int startIndex, int endIndex) + { + addMultipleObjectsStep(); + + reorderObjectStep(startIndex, endIndex); + + assertGroups(); + } + + [Test] + public void TestStackedObjects() + { + addObjectsStep(() => new OsuHitObject[] + { + new HitCircle { Position = new Vector2(300, 100) }, + new HitCircle + { + Position = new Vector2(300, 300), + StackHeight = 20 + }, + }); + + assertDirections(); + } + + private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }); + + private void addObjectsStep(Func ctorFunc) + { + AddStep("add hitobjects", () => + { + var objects = ctorFunc(); + + for (int i = 0; i < objects.Length; i++) + { + objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1); + objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + DrawableOsuHitObject drawableObject = null; + + switch (objects[i]) + { + case HitCircle circle: + drawableObject = new DrawableHitCircle(circle); + break; + + case Slider slider: + drawableObject = new DrawableSlider(slider); + break; + + case Spinner spinner: + drawableObject = new DrawableSpinner(spinner); + break; + } + + hitObjectContainer.Add(drawableObject); + followPointRenderer.AddFollowPoints2(objects[i]); + } + }); + } + + private void removeObjectStep(Func getFunc) + { + AddStep("remove hitobject", () => + { + var drawableObject = getFunc.Invoke(); + + hitObjectContainer.Remove(drawableObject); + followPointRenderer.RemoveFollowPoints2(drawableObject.HitObject); + }); + } + + private void reorderObjectStep(int startIndex, int endIndex) + { + AddStep($"move object {startIndex} to {endIndex}", () => + { + DrawableOsuHitObject toReorder = getObject(startIndex); + + double targetTime; + if (endIndex < hitObjectContainer.Count) + targetTime = getObject(endIndex).HitObject.StartTime - 1; + else + targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1; + + hitObjectContainer.Remove(toReorder); + toReorder.HitObject.StartTime = targetTime; + hitObjectContainer.Add(toReorder); + }); + } + + private void assertGroups() + { + AddAssert("has correct group count", () => followPointRenderer.Entries.Count == hitObjectContainer.Count); + AddAssert("group endpoints are correct", () => + { + for (int i = 0; i < hitObjectContainer.Count; i++) + { + DrawableOsuHitObject expectedStart = getObject(i); + DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; + + if (getEntry(i).Start != expectedStart.HitObject) + throw new AssertionException($"Object {i} expected to be the start of group {i}."); + + if (getEntry(i).End != expectedEnd?.HitObject) + throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}."); + } + + return true; + }); + } + + private void assertDirections() + { + AddAssert("group directions are correct", () => + { + for (int i = 0; i < hitObjectContainer.Count; i++) + { + DrawableOsuHitObject expectedStart = getObject(i); + DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; + + if (expectedEnd == null) + continue; + + var manualClock = new ManualClock(); + followPointRenderer.Clock = new FramedClock(manualClock); + + manualClock.CurrentTime = expectedStart.HitObject.StartTime; + followPointRenderer.UpdateSubTree(); + + var points = getGroup(i).ChildrenOfType().ToArray(); + if (points.Length == 0) + continue; + + float expectedDirection = MathF.Atan2(expectedStart.Position.Y - expectedEnd.Position.Y, expectedStart.Position.X - expectedEnd.Position.X); + float realDirection = MathF.Atan2(expectedStart.Position.Y - points[^1].Position.Y, expectedStart.Position.X - points[^1].Position.X); + + if (!Precision.AlmostEquals(expectedDirection, realDirection)) + throw new AssertionException($"Expected group {i} in direction {expectedDirection}, but was {realDirection}."); + } + + return true; + }); + } + + private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; + + private FollowPointRenderer.FollowPointLifetimeEntry getEntry(int index) => followPointRenderer.Entries[index]; + + private FollowPointConnection getGroup(int index) => followPointRenderer.ChildrenOfType().Single(c => c.Entry == getEntry(index)); + + private class TestHitObjectContainer : Container + { + protected override int Compare(Drawable x, Drawable y) + { + var osuX = (DrawableOsuHitObject)x; + var osuY = (DrawableOsuHitObject)y; + + int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime); + + if (compare == 0) + return base.Compare(x, y); + + return compare; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index ac7b78a25b..702983b74a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public override bool RemoveCompletedTransforms => false; + public IReadOnlyList Entries => lifetimeEntries; + private DrawablePool connectionPool; private DrawablePool pointPool; From 17ff7fe163499324d044cac56267c4b852c56261 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 13:55:01 +0900 Subject: [PATCH 095/394] Fix failing test due to early lifetime end --- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 702983b74a..6751585def 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -143,8 +143,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public FollowPointLifetimeEntry(OsuHitObject start) { Start = start; - - LifetimeStart = LifetimeEnd = Start.StartTime; + LifetimeStart = Start.StartTime; } private OsuHitObject end; @@ -178,6 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections double fadeInTime = fadeOutTime - FollowPointConnection.PREEMPT; LifetimeStart = fadeInTime; + LifetimeEnd = double.MaxValue; // This will be set by the connection. } } } From 2fc53a278daa7ffd27b8ba8affdb6ed1d71f226d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 14:10:28 +0900 Subject: [PATCH 096/394] Add back reorder support --- .../Connections/FollowPointRenderer.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 6751585def..6bb32238bd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -27,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private readonly List lifetimeEntries = new List(); private readonly Dictionary connectionsInUse = new Dictionary(); + private readonly Dictionary startTimeMap = new Dictionary(); private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); public FollowPointRenderer() @@ -49,6 +51,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } public void AddFollowPoints2(OsuHitObject hitObject) + { + addEntry(hitObject); + + var startTimeBindable = hitObject.StartTimeBindable.GetBoundCopy(); + startTimeBindable.ValueChanged += _ => onStartTimeChanged(hitObject); + startTimeMap[hitObject] = startTimeBindable; + } + + public void RemoveFollowPoints2(OsuHitObject hitObject) + { + removeEntry(hitObject); + + startTimeMap[hitObject].UnbindAll(); + startTimeMap.Remove(hitObject); + } + + private void addEntry(OsuHitObject hitObject) { var newEntry = new FollowPointLifetimeEntry(hitObject); @@ -95,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections lifetimeManager.AddEntry(newEntry); } - public void RemoveFollowPoints2(OsuHitObject hitObject) + private void removeEntry(OsuHitObject hitObject) { int index = lifetimeEntries.FindIndex(e => e.Start == hitObject); var entry = lifetimeEntries[index]; @@ -136,6 +155,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections connectionsInUse.Remove(entry); } + private void onStartTimeChanged(OsuHitObject hitObject) + { + removeEntry(hitObject); + addEntry(hitObject); + } + public class FollowPointLifetimeEntry : LifetimeEntry { public readonly OsuHitObject Start; From c53a8fafe6d648c1dfdc4be449e35a3d2c11bc88 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 14:25:07 +0900 Subject: [PATCH 097/394] Make test fail as expected --- osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index ef6275a7e7..a6a7f54886 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -95,9 +95,19 @@ namespace osu.Game.Rulesets.Osu.Tests { addMultipleObjectsStep(); - AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100)); + AddStep("move hitobject", () => + { + var manualClock = new ManualClock(); + followPointRenderer.Clock = new FramedClock(manualClock); + + manualClock.CurrentTime = getObject(1).HitObject.StartTime; + followPointRenderer.UpdateSubTree(); + + getObject(2).HitObject.Position = new Vector2(300, 100); + }); assertGroups(); + assertDirections(); } [TestCase(0, 0)] // Start -> Start From 8c32d3f78194b75deb5d28b390a9f14272f557c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 14:32:23 +0900 Subject: [PATCH 098/394] Don't play flair animations / sounds when watching autoplay or viewing a result directly I think this is a pretty good place to be for now. The flair will play if you just watched a play (local, replay or spectator) but will not play if you are coming from song select (viewing a replay's result screen from the leaderboard) or in the case of autoplay. Closes #10762. --- osu.Game/Screens/Ranking/ResultsScreen.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index f8bdf0140c..ce3e618889 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.API; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Play; @@ -149,7 +150,12 @@ namespace osu.Game.Screens.Ranking }; if (Score != null) - ScorePanelList.AddScore(Score, true); + { + // only show flair / animation when arriving after watching a play that isn't autoplay. + bool shouldFlair = player != null && !Score.Mods.Any(m => m is ModAutoplay); + + ScorePanelList.AddScore(Score, shouldFlair); + } if (player != null && allowRetry) { From 2db42f8e6780eb2fd67854b54413ce8506a2895a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 14:35:44 +0900 Subject: [PATCH 099/394] Remove default allowRetry parameter value from ResultsScreen --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs | 2 +- osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs | 2 +- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/SpectatorResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index ff96a999ec..44a2056732 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -326,7 +326,7 @@ namespace osu.Game.Tests.Visual.Ranking public HotkeyRetryOverlay RetryOverlay; public UnrankedSoloResultsScreen(ScoreInfo score) - : base(score) + : base(score, false) { Score.Beatmap.OnlineBeatmapID = 0; Score.Beatmap.Status = BeatmapSetOnlineStatus.Pending; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index cb0e2cfa8e..bb638bcf3a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -420,7 +420,7 @@ namespace osu.Game break; case ScorePresentType.Results: - screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo)); + screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo, false)); break; } }, validScreens: new[] { typeof(PlaySongSelect) }); diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index 04da943a10..0efa9c5196 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Multi.Play protected override ResultsScreen CreateResults(ScoreInfo score) { Debug.Assert(roomId.Value != null); - return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem); + return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem, true); } protected override ScoreInfo CreateScore() diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 8da6a530a8..3623208fa7 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Multi.Ranking [Resolved] private IAPIProvider api { get; set; } - public TimeshiftResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry = true) + public TimeshiftResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry) : base(score, allowRetry) { this.roomId = roomId; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b94f0a5062..d0a83e3c22 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -545,7 +545,7 @@ namespace osu.Game.Screens.Play protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; - protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score); + protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, true); #region Fail Logic diff --git a/osu.Game/Screens/Play/SpectatorResultsScreen.cs b/osu.Game/Screens/Play/SpectatorResultsScreen.cs index 56ccfd2253..dabdf0a139 100644 --- a/osu.Game/Screens/Play/SpectatorResultsScreen.cs +++ b/osu.Game/Screens/Play/SpectatorResultsScreen.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Play public class SpectatorResultsScreen : SoloResultsScreen { public SpectatorResultsScreen(ScoreInfo score) - : base(score) + : base(score, false) { } diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 9cf2e6757a..76b549da1a 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Ranking [Resolved] private RulesetStore rulesets { get; set; } - public SoloResultsScreen(ScoreInfo score, bool allowRetry = true) + public SoloResultsScreen(ScoreInfo score, bool allowRetry) : base(score, allowRetry) { } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index ee8825640c..50a61ed4c2 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Select } protected void PresentScore(ScoreInfo score) => - FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new SoloResultsScreen(score))); + FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new SoloResultsScreen(score, false))); protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); From af67b4a346a7147a6132415955b3f25cc99accf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 14:57:08 +0900 Subject: [PATCH 100/394] Remove no longer necessary code from OsuPlayfield --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index c816502d61..ab77be488e 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; @@ -40,8 +39,6 @@ namespace osu.Game.Rulesets.Osu.UI protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); - private readonly Bindable playfieldBorderStyle = new BindableBool(); - private readonly IDictionary> poolDictionary = new Dictionary>(); public OsuPlayfield() @@ -67,12 +64,7 @@ namespace osu.Game.Rulesets.Osu.UI RelativeSizeAxes = Axes.Both, Depth = 1, }, - // Todo: This should not exist, but currently helps to reduce LOH allocations due to unbinding skin source events on judgement disposal - // Todo: Remove when hitobjects are properly pooled - new SkinProvidingContainer(null) - { - Child = HitObjectContainer, - }, + HitObjectContainer, approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both, From 45b1fcf26fcbc3ff9befe6637a538978a3e5d01d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 15:01:37 +0900 Subject: [PATCH 101/394] Remove unnecessary using statement --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index ab77be488e..6a59cdf112 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -19,7 +19,6 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.UI From 185653b1d8b65ac4d0ce7b0ab4fe25a91b00d22b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 15:11:24 +0900 Subject: [PATCH 102/394] Remove depth specifications --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 29 ++++-------------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 6a59cdf112..0e98a1d439 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -44,31 +44,12 @@ namespace osu.Game.Rulesets.Osu.UI { InternalChildren = new Drawable[] { - playfieldBorder = new PlayfieldBorder - { - RelativeSizeAxes = Axes.Both, - Depth = 3 - }, - spinnerProxies = new ProxyContainer - { - RelativeSizeAxes = Axes.Both - }, - followPoints = new FollowPointRenderer - { - RelativeSizeAxes = Axes.Both, - Depth = 2, - }, - judgementLayer = new JudgementContainer - { - RelativeSizeAxes = Axes.Both, - Depth = 1, - }, + playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, + spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both }, + followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, + judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, HitObjectContainer, - approachCircles = new ProxyContainer - { - RelativeSizeAxes = Axes.Both, - Depth = -1, - }, + approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; hitPolicy = new OrderedHitPolicy(HitObjectContainer); From 33eea64cfc584bcd691ca3e200ad4c79f06a5956 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 15:31:04 +0900 Subject: [PATCH 103/394] Fix follow points not updating on positional changes --- .../Connections/FollowPointConnection.cs | 29 +++++++-- .../Connections/FollowPointRenderer.cs | 60 ++++++++++++++++++- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 1d82e91c0e..fe35cd02dc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -21,16 +21,33 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public FollowPointRenderer.FollowPointLifetimeEntry Entry; public DrawablePool Pool; - protected override void FreeAfterUse() - { - base.FreeAfterUse(); - ClearInternal(false); - } - protected override void PrepareForUse() { base.PrepareForUse(); + Entry.Invalidated += onEntryInvalidated; + + refreshPoints(); + } + + protected override void FreeAfterUse() + { + base.FreeAfterUse(); + + Entry.Invalidated -= onEntryInvalidated; + + // Return points to the pool. + ClearInternal(false); + + Entry = null; + } + + private void onEntryInvalidated() => refreshPoints(); + + private void refreshPoints() + { + ClearInternal(false); + OsuHitObject start = Entry.Start; OsuHitObject end = Entry.End; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 6bb32238bd..205c5c0d05 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -117,7 +118,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private void removeEntry(OsuHitObject hitObject) { int index = lifetimeEntries.FindIndex(e => e.Start == hitObject); + var entry = lifetimeEntries[index]; + entry.UnbindEvents(); lifetimeEntries.RemoveAt(index); lifetimeManager.RemoveEntry(entry); @@ -161,14 +164,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections addEntry(hitObject); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + foreach (var entry in lifetimeEntries) + entry.UnbindEvents(); + lifetimeEntries.Clear(); + } + public class FollowPointLifetimeEntry : LifetimeEntry { + public event Action Invalidated; public readonly OsuHitObject Start; public FollowPointLifetimeEntry(OsuHitObject start) { Start = start; LifetimeStart = Start.StartTime; + + bindEvents(); } private OsuHitObject end; @@ -178,12 +193,51 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections get => end; set { + UnbindEvents(); + end = value; - computeLifetimes(); + + bindEvents(); + + refreshLifetimes(); } } - private void computeLifetimes() + private void bindEvents() + { + UnbindEvents(); + + // Note: Positions are bound for instantaneous feedback from positional changes from the editor, before ApplyDefaults() is called on hitobjects. + Start.DefaultsApplied += onDefaultsApplied; + Start.PositionBindable.ValueChanged += onPositionChanged; + + if (End != null) + { + End.DefaultsApplied += onDefaultsApplied; + End.PositionBindable.ValueChanged += onPositionChanged; + } + } + + public void UnbindEvents() + { + if (Start != null) + { + Start.DefaultsApplied -= onDefaultsApplied; + Start.PositionBindable.ValueChanged -= onPositionChanged; + } + + if (End != null) + { + End.DefaultsApplied -= onDefaultsApplied; + End.PositionBindable.ValueChanged -= onPositionChanged; + } + } + + private void onDefaultsApplied(HitObject obj) => refreshLifetimes(); + + private void onPositionChanged(ValueChangedEvent obj) => refreshLifetimes(); + + private void refreshLifetimes() { if (end == null) { @@ -203,6 +257,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections LifetimeStart = fadeInTime; LifetimeEnd = double.MaxValue; // This will be set by the connection. + + Invalidated?.Invoke(); } } } From 2ed2ddfe8a05f24bcc60f84f0e6eb9b96aaf0bd7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 15:39:25 +0900 Subject: [PATCH 104/394] Rename methods --- osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs | 4 ++-- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 4 ++-- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index a6a7f54886..e7c73b941d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Tests } hitObjectContainer.Add(drawableObject); - followPointRenderer.AddFollowPoints2(objects[i]); + followPointRenderer.AddFollowPoints(objects[i]); } }); } @@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests var drawableObject = getFunc.Invoke(); hitObjectContainer.Remove(drawableObject); - followPointRenderer.RemoveFollowPoints2(drawableObject.HitObject); + followPointRenderer.RemoveFollowPoints(drawableObject.HitObject); }); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 205c5c0d05..6dea16d01f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections MakeChildAlive(pointPool); } - public void AddFollowPoints2(OsuHitObject hitObject) + public void AddFollowPoints(OsuHitObject hitObject) { addEntry(hitObject); @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections startTimeMap[hitObject] = startTimeBindable; } - public void RemoveFollowPoints2(OsuHitObject hitObject) + public void RemoveFollowPoints(OsuHitObject hitObject) { removeEntry(hitObject); diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index a8d9423bf6..c816502d61 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -125,13 +125,13 @@ namespace osu.Game.Rulesets.Osu.UI protected override void OnHitObjectAdded(HitObject hitObject) { base.OnHitObjectAdded(hitObject); - followPoints.AddFollowPoints2((OsuHitObject)hitObject); + followPoints.AddFollowPoints((OsuHitObject)hitObject); } protected override void OnHitObjectRemoved(HitObject hitObject) { base.OnHitObjectRemoved(hitObject); - followPoints.RemoveFollowPoints2((OsuHitObject)hitObject); + followPoints.RemoveFollowPoints((OsuHitObject)hitObject); } public void OnHitObjectLoaded(Drawable drawable) From 2418f17b0cd11ea01bf59a1fba1eae70e81f4ee0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 16:19:02 +0900 Subject: [PATCH 105/394] Fix lifetime not being set correctly in some cases --- .../Objects/Drawables/Connections/FollowPointConnection.cs | 3 --- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index fe35cd02dc..3f6854786b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -53,9 +53,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections double startTime = start.GetEndTime(); - if (end == null || end.NewCombo || start is Spinner || end is Spinner) - return; - Vector2 startPosition = start.StackedEndPosition; Vector2 endPosition = end.StackedPosition; double endTime = end.StartTime; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 6dea16d01f..7067cf2728 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -239,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private void refreshLifetimes() { - if (end == null) + if (End == null || End.NewCombo || Start is Spinner || End is Spinner) { LifetimeEnd = LifetimeStart; return; From d4054c87d3d86bcc54e1e8024d6182956f21e895 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 16:15:17 +0900 Subject: [PATCH 106/394] Refactor TestSceneHitCircle to show judgements --- .../TestSceneHitCircle.cs | 67 ++++++++++++------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 596bc06c68..1278a0ff2d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -1,17 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osuTK; -using osu.Game.Rulesets.Mods; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Osu.Tests { @@ -38,13 +38,37 @@ namespace osu.Game.Rulesets.Osu.Tests } private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) + { + var drawable = createSingle(circleSize, auto, timeOffset, positionOffset); + + var playfield = new TestOsuPlayfield(); + playfield.Add(drawable); + return playfield; + } + + private Drawable testStream(float circleSize, bool auto = false) + { + var playfield = new TestOsuPlayfield(); + + Vector2 pos = new Vector2(-250, 0); + + for (int i = 0; i <= 1000; i += 100) + { + playfield.Add(createSingle(circleSize, auto, i, pos)); + pos.X += 50; + } + + return playfield; + } + + private TestDrawableHitCircle createSingle(float circleSize, bool auto, double timeOffset, Vector2? positionOffset) { positionOffset ??= Vector2.Zero; var circle = new HitCircle { StartTime = Time.Current + 1000 + timeOffset, - Position = positionOffset.Value, + Position = OsuPlayfield.BASE_SIZE / 4 + positionOffset.Value, }; circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); @@ -53,31 +77,14 @@ namespace osu.Game.Rulesets.Osu.Tests foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); - return drawable; } protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) => new TestDrawableHitCircle(circle, auto) { - Anchor = Anchor.Centre, Depth = depthIndex++ }; - private Drawable testStream(float circleSize, bool auto = false) - { - var container = new Container { RelativeSizeAxes = Axes.Both }; - - Vector2 pos = new Vector2(-250, 0); - - for (int i = 0; i <= 1000; i += 100) - { - container.Add(testSingle(circleSize, auto, i, pos)); - pos.X += 50; - } - - return container; - } - protected class TestDrawableHitCircle : DrawableHitCircle { private readonly bool auto; @@ -101,5 +108,13 @@ namespace osu.Game.Rulesets.Osu.Tests base.CheckForResult(userTriggered, timeOffset); } } + + protected class TestOsuPlayfield : OsuPlayfield + { + public TestOsuPlayfield() + { + RelativeSizeAxes = Axes.Both; + } + } } } From 7fe0923fcfbbabd2befa0eda3fe07693dc7c70be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 16:14:38 +0900 Subject: [PATCH 107/394] Show main judgement content above hitobjects --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 16 ++++++++++++++-- .../Judgements/DefaultJudgementPiece.cs | 2 ++ .../Rulesets/Judgements/DrawableJudgement.cs | 19 +++++++++++++++++++ .../Judgements/IAnimatableJudgement.cs | 8 ++++++++ osu.Game/Skinning/LegacyJudgementPieceNew.cs | 4 ++++ osu.Game/Skinning/LegacyJudgementPieceOld.cs | 2 ++ 6 files changed, 49 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 0e98a1d439..3bd150c4d3 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.UI private readonly IDictionary> poolDictionary = new Dictionary>(); + private readonly Container judgementAboveHitObjectLayer; + public OsuPlayfield() { InternalChildren = new Drawable[] @@ -49,6 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, HitObjectContainer, + judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; @@ -58,13 +61,18 @@ namespace osu.Game.Rulesets.Osu.UI var hitWindows = new OsuHitWindows(); foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) - poolDictionary.Add(result, new DrawableJudgementPool(result)); + poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgmentLoaded)); AddRangeInternal(poolDictionary.Values); NewResult += onNewResult; } + private void onJudgmentLoaded(DrawableOsuJudgement judgement) + { + judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent()); + } + [BackgroundDependencyLoader(true)] private void load(OsuRulesetConfigManager config) { @@ -150,11 +158,13 @@ namespace osu.Game.Rulesets.Osu.UI private class DrawableJudgementPool : DrawablePool { private readonly HitResult result; + private readonly Action onLoaded; - public DrawableJudgementPool(HitResult result) + public DrawableJudgementPool(HitResult result, Action onLoaded) : base(10) { this.result = result; + this.onLoaded = onLoaded; } protected override DrawableOsuJudgement CreateNewDrawable() @@ -164,6 +174,8 @@ namespace osu.Game.Rulesets.Osu.UI // just a placeholder to initialise the correct drawable hierarchy for this pool. judgement.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null); + onLoaded?.Invoke(judgement); + return judgement; } } diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index b89c1f4e4f..c26dee119d 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -69,5 +69,7 @@ namespace osu.Game.Rulesets.Judgements this.FadeOutFromOne(800); } + + public Drawable GetAboveHitObjectsProxiedContent() => this.CreateProxy(); } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 4aed38e689..3063656aaf 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -29,6 +30,8 @@ namespace osu.Game.Rulesets.Judgements protected SkinnableDrawable JudgementBody { get; private set; } + private readonly Container aboveHitObjectsContent; + [Resolved] private ISkinSource skinSource { get; set; } @@ -59,6 +62,12 @@ namespace osu.Game.Rulesets.Judgements { Size = new Vector2(judgement_size); Origin = Anchor.Centre; + + AddInternal(aboveHitObjectsContent = new Container + { + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both + }); } [BackgroundDependencyLoader] @@ -67,6 +76,8 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); } + public Drawable GetProxyAboveHitObjectsContent() => aboveHitObjectsContent.CreateProxy(); + protected override void LoadComplete() { base.LoadComplete(); @@ -189,6 +200,7 @@ namespace osu.Game.Rulesets.Judgements if (JudgementBody != null) RemoveInternal(JudgementBody); + aboveHitObjectsContent.Clear(); AddInternal(JudgementBody = new SkinnableDrawable(new GameplaySkinComponent(type), _ => CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling) { @@ -196,6 +208,13 @@ namespace osu.Game.Rulesets.Judgements Origin = Anchor.Centre, }); + if (JudgementBody.Drawable is IAnimatableJudgement animatable) + { + var proxiedContent = animatable.GetAboveHitObjectsProxiedContent(); + if (proxiedContent != null) + aboveHitObjectsContent.Add(proxiedContent); + } + currentDrawableType = type; } diff --git a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs index 32312f1115..d37a270a20 100644 --- a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs @@ -10,6 +10,14 @@ namespace osu.Game.Rulesets.Judgements /// public interface IAnimatableJudgement : IDrawable { + /// + /// Start the animation for this judgement from the current point in time. + /// void PlayAnimation(); + + /// + /// Get proxied content which should be displayed above all hitobjects. + /// + Drawable GetAboveHitObjectsProxiedContent(); } } diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index 2a53820872..290571f46c 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; @@ -121,5 +122,8 @@ namespace osu.Game.Skinning break; } } + + [CanBeNull] + public Drawable GetAboveHitObjectsProxiedContent() => temporaryOldStyle?.CreateProxy(); // for new style judgements, only the old style temporary display is in front of objects. } } diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 3486dce081..5d74ab9ae3 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -69,5 +69,7 @@ namespace osu.Game.Skinning break; } } + + public Drawable GetAboveHitObjectsProxiedContent() => CreateProxy(); } } From a00e0d72793fc97724d3d94e313a9cfaa6f0438b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 16:30:48 +0900 Subject: [PATCH 108/394] Move CanBeNull specification to the interface --- osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs | 2 ++ osu.Game/Skinning/LegacyJudgementPieceNew.cs | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs index d37a270a20..b38b83b534 100644 --- a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Graphics; namespace osu.Game.Rulesets.Judgements @@ -18,6 +19,7 @@ namespace osu.Game.Rulesets.Judgements /// /// Get proxied content which should be displayed above all hitobjects. /// + [CanBeNull] Drawable GetAboveHitObjectsProxiedContent(); } } diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index 290571f46c..ca25efaa01 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; @@ -123,7 +122,6 @@ namespace osu.Game.Skinning } } - [CanBeNull] public Drawable GetAboveHitObjectsProxiedContent() => temporaryOldStyle?.CreateProxy(); // for new style judgements, only the old style temporary display is in front of objects. } } From 53b6d90ab4822e79b3c0569082580eaf2e5339df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 16:30:58 +0900 Subject: [PATCH 109/394] Don't show default judgements in front of objects for now --- osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index c26dee119d..d94346cb72 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -70,6 +70,6 @@ namespace osu.Game.Rulesets.Judgements this.FadeOutFromOne(800); } - public Drawable GetAboveHitObjectsProxiedContent() => this.CreateProxy(); + public Drawable GetAboveHitObjectsProxiedContent() => null; } } From 85eb98a7ec631531e545f896c2233445733db0af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 16:43:07 +0900 Subject: [PATCH 110/394] Clean up some code reuse --- .../Connections/FollowPointConnection.cs | 23 +++++++++++++++---- .../Connections/FollowPointRenderer.cs | 8 +++---- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 3f6854786b..f9a594a3cb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -55,12 +55,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Vector2 startPosition = start.StackedEndPosition; Vector2 endPosition = end.StackedPosition; - double endTime = end.StartTime; Vector2 distanceVector = endPosition - startPosition; int distance = (int)distanceVector.Length; float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); - double duration = endTime - startTime; double finalTransformEndTime = startTime; @@ -69,8 +67,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections float fraction = (float)d / distance; Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; Vector2 pointEndPosition = startPosition + fraction * distanceVector; - double fadeOutTime = startTime + fraction * duration; - double fadeInTime = fadeOutTime - PREEMPT; + + GetFadeTimes(start, end, (float)d / distance, out var fadeInTime, out var fadeOutTime); FollowPoint fp; @@ -98,5 +96,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections // todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed. Entry.LifetimeEnd = finalTransformEndTime; } + + /// + /// Computes the fade time of follow point positioned between two hitobjects. + /// + /// The first , where follow points should originate from. + /// The second , which follow points should target. + /// The fractional distance along and at which the follow point is to be located. + /// The fade-in time of the follow point/ + /// The fade-out time of the follow point. + public static void GetFadeTimes(OsuHitObject start, OsuHitObject end, float fraction, out double fadeInTime, out double fadeOutTime) + { + double startTime = start.GetEndTime(); + double duration = end.StartTime - startTime; + + fadeOutTime = startTime + fraction * duration; + fadeInTime = fadeOutTime - PREEMPT; + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 7067cf2728..b1914fde85 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -248,12 +248,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Vector2 startPosition = Start.StackedEndPosition; Vector2 endPosition = End.StackedPosition; Vector2 distanceVector = endPosition - startPosition; + + // The lifetime start will match the fade-in time of the first follow point. float fraction = (int)(FollowPointConnection.SPACING * 1.5) / distanceVector.Length; - - double duration = End.StartTime - Start.GetEndTime(); - - double fadeOutTime = Start.StartTime + fraction * duration; - double fadeInTime = fadeOutTime - FollowPointConnection.PREEMPT; + FollowPointConnection.GetFadeTimes(Start, End, fraction, out var fadeInTime, out _); LifetimeStart = fadeInTime; LifetimeEnd = double.MaxValue; // This will be set by the connection. From c1f56cd0ba5d2deb9b7187e9915d8b74b26ee1c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 16:49:22 +0900 Subject: [PATCH 111/394] Remove aliveness hackery --- .../Connections/FollowPointRenderer.cs | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index b1914fde85..43914c4d57 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -43,12 +43,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { InternalChildren = new Drawable[] { - connectionPool = new DrawablePool(1, 200), - pointPool = new DrawablePool(50, 1000) + connectionPool = new DrawablePoolNoLifetime(1, 200), + pointPool = new DrawablePoolNoLifetime(50, 1000) }; - - MakeChildAlive(connectionPool); - MakeChildAlive(pointPool); } public void AddFollowPoints(OsuHitObject hitObject) @@ -136,7 +133,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } } - protected override bool CheckChildrenLife() => lifetimeManager.Update(Time.Current); + protected override bool CheckChildrenLife() + { + bool anyAliveChanged = base.CheckChildrenLife(); + anyAliveChanged |= lifetimeManager.Update(Time.Current); + return anyAliveChanged; + } private void onEntryBecameAlive(LifetimeEntry entry) { @@ -149,7 +151,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections connectionsInUse[entry] = connection; AddInternal(connection); - MakeChildAlive(connection); } private void onEntryBecameDead(LifetimeEntry entry) @@ -173,6 +174,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections lifetimeEntries.Clear(); } + private class DrawablePoolNoLifetime : DrawablePool + where T : PoolableDrawable, new() + { + public override bool RemoveWhenNotAlive => false; + + public DrawablePoolNoLifetime(int initialSize, int? maximumSize = null) + : base(initialSize, maximumSize) + { + } + } + public class FollowPointLifetimeEntry : LifetimeEntry { public event Action Invalidated; From 7bd75eca816adada1857bbb1445b0f469eeb1aab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 16:53:08 +0900 Subject: [PATCH 112/394] Separate classes --- .../TestSceneFollowPoints.cs | 2 +- .../Connections/FollowPointConnection.cs | 2 +- .../Connections/FollowPointLifetimeEntry.cs | 98 +++++++++++++++++++ .../Connections/FollowPointRenderer.cs | 89 ----------------- 4 files changed, 100 insertions(+), 91 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index e7c73b941d..fe67b63252 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -272,7 +272,7 @@ namespace osu.Game.Rulesets.Osu.Tests private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; - private FollowPointRenderer.FollowPointLifetimeEntry getEntry(int index) => followPointRenderer.Entries[index]; + private FollowPointLifetimeEntry getEntry(int index) => followPointRenderer.Entries[index]; private FollowPointConnection getGroup(int index) => followPointRenderer.ChildrenOfType().Single(c => c.Entry == getEntry(index)); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index f9a594a3cb..700d96eff3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public const int SPACING = 32; public const double PREEMPT = 800; - public FollowPointRenderer.FollowPointLifetimeEntry Entry; + public FollowPointLifetimeEntry Entry; public DrawablePool Pool; protected override void PrepareForUse() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs new file mode 100644 index 0000000000..a167cb2f0f --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs @@ -0,0 +1,98 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Performance; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections +{ + public class FollowPointLifetimeEntry : LifetimeEntry + { + public event Action Invalidated; + public readonly OsuHitObject Start; + + public FollowPointLifetimeEntry(OsuHitObject start) + { + Start = start; + LifetimeStart = Start.StartTime; + + bindEvents(); + } + + private OsuHitObject end; + + public OsuHitObject End + { + get => end; + set + { + UnbindEvents(); + + end = value; + + bindEvents(); + + refreshLifetimes(); + } + } + + private void bindEvents() + { + UnbindEvents(); + + // Note: Positions are bound for instantaneous feedback from positional changes from the editor, before ApplyDefaults() is called on hitobjects. + Start.DefaultsApplied += onDefaultsApplied; + Start.PositionBindable.ValueChanged += onPositionChanged; + + if (End != null) + { + End.DefaultsApplied += onDefaultsApplied; + End.PositionBindable.ValueChanged += onPositionChanged; + } + } + + public void UnbindEvents() + { + if (Start != null) + { + Start.DefaultsApplied -= onDefaultsApplied; + Start.PositionBindable.ValueChanged -= onPositionChanged; + } + + if (End != null) + { + End.DefaultsApplied -= onDefaultsApplied; + End.PositionBindable.ValueChanged -= onPositionChanged; + } + } + + private void onDefaultsApplied(HitObject obj) => refreshLifetimes(); + + private void onPositionChanged(ValueChangedEvent obj) => refreshLifetimes(); + + private void refreshLifetimes() + { + if (End == null || End.NewCombo || Start is Spinner || End is Spinner) + { + LifetimeEnd = LifetimeStart; + return; + } + + Vector2 startPosition = Start.StackedEndPosition; + Vector2 endPosition = End.StackedPosition; + Vector2 distanceVector = endPosition - startPosition; + + // The lifetime start will match the fade-in time of the first follow point. + float fraction = (int)(FollowPointConnection.SPACING * 1.5) / distanceVector.Length; + FollowPointConnection.GetFadeTimes(Start, End, fraction, out var fadeInTime, out _); + + LifetimeStart = fadeInTime; + LifetimeEnd = double.MaxValue; // This will be set by the connection. + + Invalidated?.Invoke(); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 43914c4d57..3e85e528e8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects; -using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { @@ -184,92 +182,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { } } - - public class FollowPointLifetimeEntry : LifetimeEntry - { - public event Action Invalidated; - public readonly OsuHitObject Start; - - public FollowPointLifetimeEntry(OsuHitObject start) - { - Start = start; - LifetimeStart = Start.StartTime; - - bindEvents(); - } - - private OsuHitObject end; - - public OsuHitObject End - { - get => end; - set - { - UnbindEvents(); - - end = value; - - bindEvents(); - - refreshLifetimes(); - } - } - - private void bindEvents() - { - UnbindEvents(); - - // Note: Positions are bound for instantaneous feedback from positional changes from the editor, before ApplyDefaults() is called on hitobjects. - Start.DefaultsApplied += onDefaultsApplied; - Start.PositionBindable.ValueChanged += onPositionChanged; - - if (End != null) - { - End.DefaultsApplied += onDefaultsApplied; - End.PositionBindable.ValueChanged += onPositionChanged; - } - } - - public void UnbindEvents() - { - if (Start != null) - { - Start.DefaultsApplied -= onDefaultsApplied; - Start.PositionBindable.ValueChanged -= onPositionChanged; - } - - if (End != null) - { - End.DefaultsApplied -= onDefaultsApplied; - End.PositionBindable.ValueChanged -= onPositionChanged; - } - } - - private void onDefaultsApplied(HitObject obj) => refreshLifetimes(); - - private void onPositionChanged(ValueChangedEvent obj) => refreshLifetimes(); - - private void refreshLifetimes() - { - if (End == null || End.NewCombo || Start is Spinner || End is Spinner) - { - LifetimeEnd = LifetimeStart; - return; - } - - Vector2 startPosition = Start.StackedEndPosition; - Vector2 endPosition = End.StackedPosition; - Vector2 distanceVector = endPosition - startPosition; - - // The lifetime start will match the fade-in time of the first follow point. - float fraction = (int)(FollowPointConnection.SPACING * 1.5) / distanceVector.Length; - FollowPointConnection.GetFadeTimes(Start, End, fraction, out var fadeInTime, out _); - - LifetimeStart = fadeInTime; - LifetimeEnd = double.MaxValue; // This will be set by the connection. - - Invalidated?.Invoke(); - } - } } } From c013cd11c9ac330aa18217ce3480e7994d78c3fe Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 20 Nov 2020 17:24:09 +0900 Subject: [PATCH 113/394] Add DrawableHitObjectAdded event --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 17 +++++++++++++++++ osu.Game/Rulesets/UI/Playfield.cs | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 5fbda305c8..5dc653395b 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -42,6 +42,11 @@ namespace osu.Game.Rulesets.UI /// public event Action RevertResult; + /// + /// Invoked when a is added. + /// + public event Action DrawableHitObjectAdded; + /// /// Invoked when a becomes used by a . /// @@ -115,6 +120,8 @@ namespace osu.Game.Rulesets.UI bindStartTime(drawable); AddInternal(drawableMap[entry] = drawable, false); + DrawableHitObjectAdded?.Invoke(drawable); + HitObjectUsageBegan?.Invoke(entry.HitObject); } @@ -147,6 +154,16 @@ namespace osu.Game.Rulesets.UI hitObject.OnRevertResult += onRevertResult; AddInternal(hitObject); + + onDrawableHitObjectAddedRecursive(hitObject); + } + + private void onDrawableHitObjectAddedRecursive(DrawableHitObject hitObject) + { + DrawableHitObjectAdded?.Invoke(hitObject); + + foreach (var nested in hitObject.NestedHitObjects) + onDrawableHitObjectAddedRecursive(nested); } public virtual bool Remove(DrawableHitObject hitObject) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 82ec653f31..245fdd59e5 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -32,6 +32,11 @@ namespace osu.Game.Rulesets.UI /// public event Action RevertResult; + /// + /// Invoked when a is added. + /// + public event Action DrawableHitObjectAdded; + /// /// The contained in this Playfield. /// @@ -91,6 +96,7 @@ namespace osu.Game.Rulesets.UI { h.NewResult += (d, r) => NewResult?.Invoke(d, r); h.RevertResult += (d, r) => RevertResult?.Invoke(d, r); + h.DrawableHitObjectAdded += d => DrawableHitObjectAdded?.Invoke(d); h.HitObjectUsageBegan += o => HitObjectUsageBegan?.Invoke(o); h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o); })); From 468b2a97cbcf40f71a8df3e3c067617c7a197cff Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 20 Nov 2020 17:25:57 +0900 Subject: [PATCH 114/394] Use events instead of overriding Add (catch) --- .../Objects/Drawables/DrawableBananaShower.cs | 2 +- .../Objects/Drawables/DrawableJuiceStream.cs | 3 +-- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 15 ++++----------- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs index 4ce80aceb8..b46933d0c2 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables switch (hitObject) { case Banana banana: - return createDrawableRepresentation?.Invoke(banana)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false); + return createDrawableRepresentation?.Invoke(banana); } return base.CreateNestedHitObject(hitObject); diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs index 7bc016d94f..cc8accbb2b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs @@ -47,8 +47,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables switch (hitObject) { case CatchHitObject catchObject: - return createDrawableRepresentation?.Invoke(catchObject)?.With(o => - ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false); + return createDrawableRepresentation?.Invoke(catchObject); } throw new ArgumentException($"{nameof(hitObject)} must be of type {nameof(CatchHitObject)}."); diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 735d7fc300..0b379bbe95 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -55,21 +55,14 @@ namespace osu.Game.Rulesets.Catch.UI HitObjectContainer, CatcherArea, }; + + NewResult += onNewResult; + RevertResult += onRevertResult; + DrawableHitObjectAdded += d => ((DrawableCatchHitObject)d).CheckPosition = CheckIfWeCanCatch; } public bool CheckIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj); - public override void Add(DrawableHitObject h) - { - h.OnNewResult += onNewResult; - h.OnRevertResult += onRevertResult; - - base.Add(h); - - var fruit = (DrawableCatchHitObject)h; - fruit.CheckPosition = CheckIfWeCanCatch; - } - private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) => CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result); From cd16a3fa614443cc150c74d407081447942ea58b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 20 Nov 2020 17:28:06 +0900 Subject: [PATCH 115/394] Use event instead of using custom pools (osu) --- .../Edit/DrawableOsuEditPool.cs | 63 ------------------- .../Edit/DrawableOsuEditRuleset.cs | 48 +++++++++++++- .../Objects/Drawables/DrawableOsuPool.cs | 32 ---------- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 38 +++++------ 4 files changed, 64 insertions(+), 117 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs delete mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuPool.cs diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs deleted file mode 100644 index 776aacd143..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Linq; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables; - -namespace osu.Game.Rulesets.Osu.Edit -{ - public class DrawableOsuEditPool : DrawableOsuPool - where T : DrawableHitObject, new() - { - /// - /// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay. - /// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points. - /// - private const double editor_hit_object_fade_out_extension = 700; - - public DrawableOsuEditPool(Func checkHittable, Action onLoaded, int initialSize, int? maximumSize = null) - : base(checkHittable, onLoaded, initialSize, maximumSize) - { - } - - protected override T CreateNewDrawable() => base.CreateNewDrawable().With(d => d.ApplyCustomUpdateState += updateState); - - private void updateState(DrawableHitObject hitObject, ArmedState state) - { - if (state == ArmedState.Idle) - return; - - // adjust the visuals of certain object types to make them stay on screen for longer than usual. - switch (hitObject) - { - default: - // there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.) - return; - - case DrawableSlider _: - // no specifics to sliders but let them fade slower below. - break; - - case DrawableHitCircle circle: // also handles slider heads - circle.ApproachCircle - .FadeOutFromOne(editor_hit_object_fade_out_extension) - .Expire(); - break; - } - - // Get the existing fade out transform - var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); - - if (existing == null) - return; - - hitObject.RemoveTransform(existing); - - using (hitObject.BeginAbsoluteSequence(existing.StartTime)) - hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); - } - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 547dff88b5..a03389bfe9 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -2,9 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Graphics.Pooling; +using System.Linq; +using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -26,8 +29,47 @@ namespace osu.Game.Rulesets.Osu.Edit { protected override GameplayCursorContainer CreateCursor() => null; - protected override DrawablePool CreatePool(int initialSize, int? maximumSize = null) - => new DrawableOsuEditPool(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize); + public OsuEditPlayfield() + { + DrawableHitObjectAdded += d => d.ApplyCustomUpdateState += updateState; + } + + private const double editor_hit_object_fade_out_extension = 700; + + private void updateState(DrawableHitObject hitObject, ArmedState state) + { + if (state == ArmedState.Idle) + return; + + // adjust the visuals of certain object types to make them stay on screen for longer than usual. + switch (hitObject) + { + default: + // there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.) + return; + + case DrawableSlider _: + // no specifics to sliders but let them fade slower below. + break; + + case DrawableHitCircle circle: // also handles slider heads + circle.ApproachCircle + .FadeOutFromOne(editor_hit_object_fade_out_extension) + .Expire(); + break; + } + + // Get the existing fade out transform + var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); + + if (existing == null) + return; + + hitObject.RemoveTransform(existing); + + using (hitObject.BeginAbsoluteSequence(existing.StartTime)) + hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); + } } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuPool.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuPool.cs deleted file mode 100644 index 1b5fd50022..0000000000 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuPool.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Pooling; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - public class DrawableOsuPool : DrawablePool - where T : DrawableHitObject, new() - { - private readonly Func checkHittable; - private readonly Action onLoaded; - - public DrawableOsuPool(Func checkHittable, Action onLoaded, int initialSize, int? maximumSize = null) - : base(initialSize, maximumSize) - { - this.checkHittable = checkHittable; - this.onLoaded = onLoaded; - } - - protected override T CreateNewDrawable() => base.CreateNewDrawable().With(o => - { - var osuObject = (DrawableOsuHitObject)(object)o; - - osuObject.CheckHittable = checkHittable; - osuObject.OnLoadComplete += onLoaded; - }); - } -} diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index c816502d61..1b0d50b4f3 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -91,6 +91,15 @@ namespace osu.Game.Rulesets.Osu.UI AddRangeInternal(poolDictionary.Values); NewResult += onNewResult; + DrawableHitObjectAdded += onDrawableHitObjectAdded; + } + + private void onDrawableHitObjectAdded(DrawableHitObject drawable) + { + if (!drawable.IsLoaded) + drawable.OnLoadComplete += onDrawableHitObjectLoaded; + + ((DrawableOsuHitObject)drawable).CheckHittable = CheckHittable; } [BackgroundDependencyLoader(true)] @@ -98,28 +107,19 @@ namespace osu.Game.Rulesets.Osu.UI { config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle); - registerPool(10, 100); + RegisterPool(10, 100); - registerPool(10, 100); - registerPool(10, 100); - registerPool(10, 100); - registerPool(10, 100); - registerPool(5, 50); + RegisterPool(10, 100); + RegisterPool(10, 100); + RegisterPool(10, 100); + RegisterPool(10, 100); + RegisterPool(5, 50); - registerPool(2, 20); - registerPool(10, 100); - registerPool(10, 100); + RegisterPool(2, 20); + RegisterPool(10, 100); + RegisterPool(10, 100); } - private void registerPool(int initialSize, int? maximumSize = null) - where TObject : HitObject - where TDrawable : DrawableHitObject, new() - => RegisterPool(CreatePool(initialSize, maximumSize)); - - protected virtual DrawablePool CreatePool(int initialSize, int? maximumSize = null) - where TDrawable : DrawableHitObject, new() - => new DrawableOsuPool(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize); - protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject); protected override void OnHitObjectAdded(HitObject hitObject) @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.UI followPoints.RemoveFollowPoints((OsuHitObject)hitObject); } - public void OnHitObjectLoaded(Drawable drawable) + private void onDrawableHitObjectLoaded(Drawable drawable) { switch (drawable) { From 772f6df668e7d95c14bb5ba9e6e84a69ba693ba3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 20 Nov 2020 18:00:00 +0900 Subject: [PATCH 116/394] Add a remark for DrawableHitObjectAdded --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 3 +++ osu.Game/Rulesets/UI/Playfield.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 5dc653395b..da7f5a0ee5 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -45,6 +45,9 @@ namespace osu.Game.Rulesets.UI /// /// Invoked when a is added. /// + /// + /// This event is also called for nested s. + /// public event Action DrawableHitObjectAdded; /// diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 245fdd59e5..be56be91d5 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -35,6 +35,9 @@ namespace osu.Game.Rulesets.UI /// /// Invoked when a is added. /// + /// + /// This event is also called for nested s. + /// public event Action DrawableHitObjectAdded; /// From 27f5a99726c238effb4311e0324dc589f30a1d08 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 20 Nov 2020 18:42:48 +0900 Subject: [PATCH 117/394] Fix more than one proxy is created --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 37 +++++++++--------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 1b0d50b4f3..d453b9cd53 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -96,10 +96,20 @@ namespace osu.Game.Rulesets.Osu.UI private void onDrawableHitObjectAdded(DrawableHitObject drawable) { - if (!drawable.IsLoaded) - drawable.OnLoadComplete += onDrawableHitObjectLoaded; - ((DrawableOsuHitObject)drawable).CheckHittable = CheckHittable; + + switch (drawable) + { + case DrawableSpinner _: + if (!drawable.HasProxy) + spinnerProxies.Add(drawable.CreateProxy()); + break; + + case IDrawableHitObjectWithProxiedApproach approach: + if (!approach.ProxiedLayer.HasProxy) + approachCircles.Add(approach.ProxiedLayer.CreateProxy()); + break; + } } [BackgroundDependencyLoader(true)] @@ -134,27 +144,6 @@ namespace osu.Game.Rulesets.Osu.UI followPoints.RemoveFollowPoints((OsuHitObject)hitObject); } - private void onDrawableHitObjectLoaded(Drawable drawable) - { - switch (drawable) - { - case DrawableSliderHead _: - case DrawableSliderTail _: - case DrawableSliderTick _: - case DrawableSliderRepeat _: - case DrawableSpinnerTick _: - break; - - case DrawableSpinner _: - spinnerProxies.Add(drawable.CreateProxy()); - break; - - case IDrawableHitObjectWithProxiedApproach approach: - approachCircles.Add(approach.ProxiedLayer.CreateProxy()); - break; - } - } - private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { // Hitobjects that block future hits should miss previous hitobjects if they're hit out-of-order. From a26b0915b4047d61032dc7c0e9fc893502ac70ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 20:08:05 +0900 Subject: [PATCH 118/394] Fix scheduled tasks not being cleaned up between test steps --- .../TestSceneShaking.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs index d692be89b2..7e973d0971 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.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 System.Collections.Generic; using System.Diagnostics; +using osu.Framework.Threading; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -10,6 +13,19 @@ namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneShaking : TestSceneHitCircle { + private readonly List scheduledTasks = new List(); + + protected override IBeatmap CreateBeatmapForSkinProvider() + { + // best way to run cleanup before a new step is run + foreach (var task in scheduledTasks) + task.Cancel(); + + scheduledTasks.Clear(); + + return base.CreateBeatmapForSkinProvider(); + } + protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) { var drawableHitObject = base.CreateDrawableHitCircle(circle, auto); @@ -17,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests Debug.Assert(drawableHitObject.HitObject.HitWindows != null); double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current; - Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay); + scheduledTasks.Add(Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay)); return drawableHitObject; } From 96abee3fde991e91f1fe14663a9e130c4b4a7005 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 22:43:10 +0900 Subject: [PATCH 119/394] Fix silent NRE in slider selection blueprint --- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 7ae4f387ca..d592e129d9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -44,6 +44,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } + private readonly BindableList controlPoints = new BindableList(); + private readonly IBindable pathVersion = new Bindable(); + public SliderSelectionBlueprint(DrawableSlider slider) : base(slider) { @@ -61,13 +64,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders }; } - private IBindable pathVersion; - protected override void LoadComplete() { base.LoadComplete(); - pathVersion = HitObject.Path.Version.GetBoundCopy(); + controlPoints.BindTo(HitObject.Path.ControlPoints); + + pathVersion.BindTo(HitObject.Path.Version); pathVersion.BindValueChanged(_ => updatePath()); BodyPiece.UpdateFrom(HitObject); @@ -164,8 +167,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } } - private BindableList controlPoints => HitObject.Path.ControlPoints; - private int addControlPoint(Vector2 position) { position -= HitObject.Position; From 82aefa3868030e0fd323710a1153b30e890899ab Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sat, 21 Nov 2020 00:27:19 +0900 Subject: [PATCH 120/394] Rework and rename to OnNewDrawableHitObject. The semantics is changed and hopefully more clear. --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 2 +- .../Edit/DrawableOsuEditRuleset.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 18 +++++++++++------ .../Objects/Drawables/DrawableHitObject.cs | 12 ++++++++++- osu.Game/Rulesets/UI/HitObjectContainer.cs | 20 ------------------- osu.Game/Rulesets/UI/Playfield.cs | 14 ++++++++++--- 6 files changed, 36 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 0b379bbe95..cd246e78d5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Catch.UI NewResult += onNewResult; RevertResult += onRevertResult; - DrawableHitObjectAdded += d => ((DrawableCatchHitObject)d).CheckPosition = CheckIfWeCanCatch; + OnNewDrawableHitObject += d => ((DrawableCatchHitObject)d).CheckPosition = CheckIfWeCanCatch; } public bool CheckIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj); diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index a03389bfe9..1a71a88c71 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Edit public OsuEditPlayfield() { - DrawableHitObjectAdded += d => d.ApplyCustomUpdateState += updateState; + OnNewDrawableHitObject += d => d.ApplyCustomUpdateState += updateState; } private const double editor_hit_object_fade_out_extension = 700; diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index d453b9cd53..d7336050eb 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -91,23 +92,28 @@ namespace osu.Game.Rulesets.Osu.UI AddRangeInternal(poolDictionary.Values); NewResult += onNewResult; - DrawableHitObjectAdded += onDrawableHitObjectAdded; + OnNewDrawableHitObject += onDrawableHitObjectAdded; } private void onDrawableHitObjectAdded(DrawableHitObject drawable) { ((DrawableOsuHitObject)drawable).CheckHittable = CheckHittable; + Debug.Assert(!drawable.IsLoaded, $"Already loaded {nameof(DrawableHitObject)} is added to {nameof(OsuPlayfield)}"); + drawable.OnLoadComplete += onDrawableHitObjectLoaded; + } + + private void onDrawableHitObjectLoaded(Drawable drawable) + { + // note: `Slider`'s `ProxiedLayer` is added when its nested `DrawableHitCircle` is loaded. switch (drawable) { case DrawableSpinner _: - if (!drawable.HasProxy) - spinnerProxies.Add(drawable.CreateProxy()); + spinnerProxies.Add(drawable.CreateProxy()); break; - case IDrawableHitObjectWithProxiedApproach approach: - if (!approach.ProxiedLayer.HasProxy) - approachCircles.Add(approach.ProxiedLayer.CreateProxy()); + case DrawableHitCircle hitCircle: + approachCircles.Add(hitCircle.ProxiedLayer.CreateProxy()); break; } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ca49ed9e75..312fbaa2d1 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -74,6 +74,11 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public event Action OnRevertResult; + /// + /// Invoked when a new nested hit object is created by . + /// + internal event Action OnNestedDrawableCreated; + /// /// Whether a visual indicator should be displayed when a scoring result occurs. /// @@ -214,10 +219,15 @@ namespace osu.Game.Rulesets.Objects.Drawables foreach (var h in HitObject.NestedHitObjects) { - var drawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h) + var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h); + var drawableNested = pooledDrawableNested ?? CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); + // Invoke the event only if this nested object is just created by `CreateNestedHitObject`. + if (pooledDrawableNested == null) + OnNestedDrawableCreated?.Invoke(drawableNested); + drawableNested.OnNewResult += onNewResult; drawableNested.OnRevertResult += onRevertResult; drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index da7f5a0ee5..5fbda305c8 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -42,14 +42,6 @@ namespace osu.Game.Rulesets.UI /// public event Action RevertResult; - /// - /// Invoked when a is added. - /// - /// - /// This event is also called for nested s. - /// - public event Action DrawableHitObjectAdded; - /// /// Invoked when a becomes used by a . /// @@ -123,8 +115,6 @@ namespace osu.Game.Rulesets.UI bindStartTime(drawable); AddInternal(drawableMap[entry] = drawable, false); - DrawableHitObjectAdded?.Invoke(drawable); - HitObjectUsageBegan?.Invoke(entry.HitObject); } @@ -157,16 +147,6 @@ namespace osu.Game.Rulesets.UI hitObject.OnRevertResult += onRevertResult; AddInternal(hitObject); - - onDrawableHitObjectAddedRecursive(hitObject); - } - - private void onDrawableHitObjectAddedRecursive(DrawableHitObject hitObject) - { - DrawableHitObjectAdded?.Invoke(hitObject); - - foreach (var nested in hitObject.NestedHitObjects) - onDrawableHitObjectAddedRecursive(nested); } public virtual bool Remove(DrawableHitObject hitObject) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index be56be91d5..8723a8c531 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -33,12 +33,14 @@ namespace osu.Game.Rulesets.UI public event Action RevertResult; /// - /// Invoked when a is added. + /// Invoked before a new is added. + /// This event is invoked only once for each + /// even the drawable is pooled and used multiple times for different s. /// /// /// This event is also called for nested s. /// - public event Action DrawableHitObjectAdded; + public event Action OnNewDrawableHitObject; /// /// The contained in this Playfield. @@ -93,13 +95,15 @@ namespace osu.Game.Rulesets.UI /// protected Playfield() { + OnNewDrawableHitObject += d => + d.OnNestedDrawableCreated += nested => OnNewDrawableHitObject?.Invoke(nested); + RelativeSizeAxes = Axes.Both; hitObjectContainerLazy = new Lazy(() => CreateHitObjectContainer().With(h => { h.NewResult += (d, r) => NewResult?.Invoke(d, r); h.RevertResult += (d, r) => RevertResult?.Invoke(d, r); - h.DrawableHitObjectAdded += d => DrawableHitObjectAdded?.Invoke(d); h.HitObjectUsageBegan += o => HitObjectUsageBegan?.Invoke(o); h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o); })); @@ -133,6 +137,8 @@ namespace osu.Game.Rulesets.UI /// The DrawableHitObject to add. public virtual void Add(DrawableHitObject h) { + OnNewDrawableHitObject?.Invoke(h); + HitObjectContainer.Add(h); OnHitObjectAdded(h.HitObject); } @@ -334,6 +340,8 @@ namespace osu.Game.Rulesets.UI // This is done before Apply() so that the state is updated once when the hitobject is applied. if (!dho.IsLoaded) { + OnNewDrawableHitObject?.Invoke(dho); + foreach (var m in mods.OfType()) m.ApplyToDrawableHitObjects(dho.Yield()); } From c4cb1440ab0a02701f3501b04aa73ef3e8e3cdc3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 21 Nov 2020 02:59:01 +0300 Subject: [PATCH 121/394] Rename PaginatedContainerHeader to ProfileSubsectionHeader --- ...ntainerHeader.cs => TestSceneProfileSubsectionHeader.cs} | 6 +++--- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 4 ++-- ...ginatedContainerHeader.cs => ProfileSubsectionHeader.cs} | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestScenePaginatedContainerHeader.cs => TestSceneProfileSubsectionHeader.cs} (95%) rename osu.Game/Overlays/Profile/Sections/{PaginatedContainerHeader.cs => ProfileSubsectionHeader.cs} (95%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs similarity index 95% rename from osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs index 2e9f919cfd..cd226662d7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs @@ -11,12 +11,12 @@ using osu.Framework.Allocation; namespace osu.Game.Tests.Visual.UserInterface { - public class TestScenePaginatedContainerHeader : OsuTestScene + public class TestSceneProfileSubsectionHeader : OsuTestScene { [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); - private PaginatedContainerHeader header; + private ProfileSubsectionHeader header; [Test] public void TestHiddenCounter() @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void createHeader(string text, CounterVisibilityState state, int initialValue = 0) { Clear(); - Add(header = new PaginatedContainerHeader(text, state) + Add(header = new ProfileSubsectionHeader(text, state) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index c1107ce907..5bcd7d34c2 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Profile.Sections private readonly string missingText; private ShowMoreButton moreButton; private OsuSpriteText missing; - private PaginatedContainerHeader header; + private ProfileSubsectionHeader header; private readonly string headerText; private readonly CounterVisibilityState counterVisibilityState; @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Profile.Sections Children = new Drawable[] { - header = new PaginatedContainerHeader(headerText, counterVisibilityState) + header = new ProfileSubsectionHeader(headerText, counterVisibilityState) { Alpha = string.IsNullOrEmpty(headerText) ? 0 : 1 }, diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs similarity index 95% rename from osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs rename to osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs index 8c617e5fbd..5858cebe89 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics; namespace osu.Game.Overlays.Profile.Sections { - public class PaginatedContainerHeader : CompositeDrawable, IHasCurrentValue + public class ProfileSubsectionHeader : CompositeDrawable, IHasCurrentValue { private readonly BindableWithCurrent current = new BindableWithCurrent(); @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Profile.Sections private CounterPill counterPill; - public PaginatedContainerHeader(string text, CounterVisibilityState counterState) + public ProfileSubsectionHeader(string text, CounterVisibilityState counterState) { this.text = text; this.counterState = counterState; From 718ba9253bce4351e2dc07aaa495c943b70b9803 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 21 Nov 2020 03:18:24 +0300 Subject: [PATCH 122/394] Implement ProfileSubsection component --- .../Beatmaps/PaginatedBeatmapContainer.cs | 2 +- .../PaginatedMostPlayedBeatmapContainer.cs | 2 +- .../Kudosu/PaginatedKudosuHistoryContainer.cs | 2 +- ...ainer.cs => PaginatedProfileSubsection.cs} | 66 ++++------------ .../Profile/Sections/ProfileSubsection.cs | 78 +++++++++++++++++++ .../Sections/Ranks/PaginatedScoreContainer.cs | 2 +- .../PaginatedRecentActivityContainer.cs | 2 +- 7 files changed, 100 insertions(+), 54 deletions(-) rename osu.Game/Overlays/Profile/Sections/{PaginatedContainer.cs => PaginatedProfileSubsection.cs} (61%) create mode 100644 osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 4b7de8de90..780d7ea986 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Overlays.Profile.Sections.Beatmaps { - public class PaginatedBeatmapContainer : PaginatedContainer + public class PaginatedBeatmapContainer : PaginatedProfileSubsection { private const float panel_padding = 10f; private readonly BeatmapSetType type; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 556f3139dd..e5bb1f8008 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -13,7 +13,7 @@ using osu.Game.Users; namespace osu.Game.Overlays.Profile.Sections.Historical { - public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer + public class PaginatedMostPlayedBeatmapContainer : PaginatedProfileSubsection { public PaginatedMostPlayedBeatmapContainer(Bindable user) : base(user, "Most Played Beatmaps", "No records. :(", CounterVisibilityState.AlwaysVisible) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs index 1b8bd23eb4..008d89d881 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs @@ -11,7 +11,7 @@ using System.Collections.Generic; namespace osu.Game.Overlays.Profile.Sections.Kudosu { - public class PaginatedKudosuHistoryContainer : PaginatedContainer + public class PaginatedKudosuHistoryContainer : PaginatedProfileSubsection { public PaginatedKudosuHistoryContainer(Bindable user) : base(user, missingText: "This user hasn't received any kudosu!") diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs similarity index 61% rename from osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs rename to osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 5bcd7d34c2..1f897d704a 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -6,10 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.API; -using osu.Game.Rulesets; using osu.Game.Users; using System.Collections.Generic; using System.Linq; @@ -18,7 +15,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Profile.Sections { - public abstract class PaginatedContainer : FillFlowContainer + public abstract class PaginatedProfileSubsection : ProfileSubsection { [Resolved] private IAPIProvider api { get; set; } @@ -26,42 +23,25 @@ namespace osu.Game.Overlays.Profile.Sections protected int VisiblePages; protected int ItemsPerPage; - protected readonly Bindable User = new Bindable(); - protected FillFlowContainer ItemsContainer; - protected RulesetStore Rulesets; + protected FillFlowContainer ItemsContainer { get; private set; } private APIRequest> retrievalRequest; private CancellationTokenSource loadCancellation; - private readonly string missingText; private ShowMoreButton moreButton; - private OsuSpriteText missing; - private ProfileSubsectionHeader header; - private readonly string headerText; - private readonly CounterVisibilityState counterVisibilityState; - - protected PaginatedContainer(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + protected PaginatedProfileSubsection(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + : base(user, headerText, missingText, counterVisibilityState) { - this.headerText = headerText; - this.missingText = missingText; - this.counterVisibilityState = counterVisibilityState; - User.BindTo(user); } - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) + protected override Drawable CreateContent() => new FillFlowContainer { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; - + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new ProfileSubsectionHeader(headerText, counterVisibilityState) - { - Alpha = string.IsNullOrEmpty(headerText) ? 0 : 1 - }, ItemsContainer = new FillFlowContainer { AutoSizeAxes = Axes.Y, @@ -75,22 +55,11 @@ namespace osu.Game.Overlays.Profile.Sections Alpha = 0, Margin = new MarginPadding { Top = 10 }, Action = showMore, - }, - missing = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 15), - Text = missingText, - Alpha = 0, - }, - }; + } + } + }; - Rulesets = rulesets; - - User.ValueChanged += onUserChanged; - User.TriggerChange(); - } - - private void onUserChanged(ValueChangedEvent e) + protected override void OnUserChanged(ValueChangedEvent e) { loadCancellation?.Cancel(); retrievalRequest?.Cancel(); @@ -124,15 +93,15 @@ namespace osu.Game.Overlays.Profile.Sections moreButton.Hide(); moreButton.IsLoading = false; - if (!string.IsNullOrEmpty(missing.Text)) - missing.Show(); + if (!string.IsNullOrEmpty(Missing.Text)) + Missing.Show(); return; } LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null), drawables => { - missing.Hide(); + Missing.Hide(); moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); moreButton.IsLoading = false; @@ -142,8 +111,6 @@ namespace osu.Game.Overlays.Profile.Sections protected virtual int GetCount(User user) => 0; - protected void SetCount(int value) => header.Current.Value = value; - protected virtual void OnItemsReceived(List items) { } @@ -154,8 +121,9 @@ namespace osu.Game.Overlays.Profile.Sections protected override void Dispose(bool isDisposing) { - base.Dispose(isDisposing); retrievalRequest?.Cancel(); + loadCancellation?.Cancel(); + base.Dispose(isDisposing); } } } diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs new file mode 100644 index 0000000000..751b35e342 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Users; +using JetBrains.Annotations; + +namespace osu.Game.Overlays.Profile.Sections +{ + public abstract class ProfileSubsection : FillFlowContainer + { + protected readonly Bindable User = new Bindable(); + + protected RulesetStore Rulesets { get; private set; } + + protected OsuSpriteText Missing { get; private set; } + + private readonly string headerText; + private readonly string missingText; + private readonly CounterVisibilityState counterVisibilityState; + + private ProfileSubsectionHeader header; + + protected ProfileSubsection(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + { + this.headerText = headerText; + this.missingText = missingText; + this.counterVisibilityState = counterVisibilityState; + User.BindTo(user); + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + + Children = new[] + { + header = new ProfileSubsectionHeader(headerText, counterVisibilityState) + { + Alpha = string.IsNullOrEmpty(headerText) ? 0 : 1 + }, + CreateContent(), + Missing = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 15), + Text = missingText, + Alpha = 0, + }, + }; + + Rulesets = rulesets; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + User.BindValueChanged(OnUserChanged, true); + } + + [NotNull] + protected abstract Drawable CreateContent(); + + protected virtual void OnUserChanged(ValueChangedEvent e) + { + } + + protected void SetCount(int value) => header.Current.Value = value; + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 1ce3079d52..53f6d375ca 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -14,7 +14,7 @@ using osu.Framework.Allocation; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedContainer + public class PaginatedScoreContainer : PaginatedProfileSubsection { private readonly ScoreType type; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index 08f39c6272..d7101a8147 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -13,7 +13,7 @@ using osu.Framework.Allocation; namespace osu.Game.Overlays.Profile.Sections.Recent { - public class PaginatedRecentActivityContainer : PaginatedContainer + public class PaginatedRecentActivityContainer : PaginatedProfileSubsection { public PaginatedRecentActivityContainer(Bindable user) : base(user, missingText: "This user hasn't done anything notable recently!") From 281ed49332c3a4f795eac9619bab786a5c1ee977 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sat, 21 Nov 2020 11:19:52 +0900 Subject: [PATCH 123/394] Add `HasInitialized` to DHO As it turned out, `IsLoaded` is not a reliable way. --- .../Objects/Drawables/DrawableHitObject.cs | 5 ++++ osu.Game/Rulesets/UI/Playfield.cs | 27 +++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 312fbaa2d1..84b2dd7957 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -146,6 +146,11 @@ namespace osu.Game.Rulesets.Objects.Drawables private Container samplesContainer; + /// + /// Whether the initialization logic in has applied. + /// + internal bool HasInitialized; + /// /// Creates a new . /// diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 8723a8c531..f0b63fd347 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osuTK; +using System.Diagnostics; namespace osu.Game.Rulesets.UI { @@ -95,9 +96,6 @@ namespace osu.Game.Rulesets.UI /// protected Playfield() { - OnNewDrawableHitObject += d => - d.OnNestedDrawableCreated += nested => OnNewDrawableHitObject?.Invoke(nested); - RelativeSizeAxes = Axes.Both; hitObjectContainerLazy = new Lazy(() => CreateHitObjectContainer().With(h => @@ -126,6 +124,16 @@ namespace osu.Game.Rulesets.UI } } + private void onNewDrawableHitObject(DrawableHitObject d) + { + d.OnNestedDrawableCreated += onNewDrawableHitObject; + + OnNewDrawableHitObject?.Invoke(d); + + Debug.Assert(!d.HasInitialized); + d.HasInitialized = true; + } + /// /// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield. /// @@ -137,7 +145,10 @@ namespace osu.Game.Rulesets.UI /// The DrawableHitObject to add. public virtual void Add(DrawableHitObject h) { - OnNewDrawableHitObject?.Invoke(h); + if (h.HasInitialized) + throw new InvalidOperationException($"{nameof(Playfield.Add)} doesn't support {nameof(DrawableHitObject)} reuse. Use pooling instead."); + + onNewDrawableHitObject(h); HitObjectContainer.Add(h); OnHitObjectAdded(h.HitObject); @@ -336,12 +347,12 @@ namespace osu.Game.Rulesets.UI { var dho = (DrawableHitObject)d; - // If this is the first time this DHO is being used (not loaded), then apply the DHO mods. - // This is done before Apply() so that the state is updated once when the hitobject is applied. - if (!dho.IsLoaded) + if (!dho.HasInitialized) { - OnNewDrawableHitObject?.Invoke(dho); + onNewDrawableHitObject(dho); + // If this is the first time this DHO is being used, then apply the DHO mods. + // This is done before Apply() so that the state is updated once when the hitobject is applied. foreach (var m in mods.OfType()) m.ApplyToDrawableHitObjects(dho.Yield()); } From 4345d8dcb641f5838c707e0556dfbaa49dcd821a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sat, 21 Nov 2020 15:20:33 +0900 Subject: [PATCH 124/394] Event -> virtual method --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 6 ++++- .../Edit/DrawableOsuEditRuleset.cs | 4 ++-- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 3 +-- osu.Game/Rulesets/UI/Playfield.cs | 23 ++++++++++--------- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index cd246e78d5..7d8f18ee0b 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -58,7 +58,11 @@ namespace osu.Game.Rulesets.Catch.UI NewResult += onNewResult; RevertResult += onRevertResult; - OnNewDrawableHitObject += d => ((DrawableCatchHitObject)d).CheckPosition = CheckIfWeCanCatch; + } + + protected override void OnNewDrawableHitObject(DrawableHitObject d) + { + ((DrawableCatchHitObject)d).CheckPosition = CheckIfWeCanCatch; } public bool CheckIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj); diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 1a71a88c71..dafde0b927 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -29,9 +29,9 @@ namespace osu.Game.Rulesets.Osu.Edit { protected override GameplayCursorContainer CreateCursor() => null; - public OsuEditPlayfield() + protected override void OnNewDrawableHitObject(DrawableHitObject d) { - OnNewDrawableHitObject += d => d.ApplyCustomUpdateState += updateState; + d.ApplyCustomUpdateState += updateState; } private const double editor_hit_object_fade_out_extension = 700; diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index d7336050eb..f70229fc1b 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -92,10 +92,9 @@ namespace osu.Game.Rulesets.Osu.UI AddRangeInternal(poolDictionary.Values); NewResult += onNewResult; - OnNewDrawableHitObject += onDrawableHitObjectAdded; } - private void onDrawableHitObjectAdded(DrawableHitObject drawable) + protected override void OnNewDrawableHitObject(DrawableHitObject drawable) { ((DrawableOsuHitObject)drawable).CheckHittable = CheckHittable; diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index f0b63fd347..fcdd5ff53d 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -33,16 +33,6 @@ namespace osu.Game.Rulesets.UI /// public event Action RevertResult; - /// - /// Invoked before a new is added. - /// This event is invoked only once for each - /// even the drawable is pooled and used multiple times for different s. - /// - /// - /// This event is also called for nested s. - /// - public event Action OnNewDrawableHitObject; - /// /// The contained in this Playfield. /// @@ -128,7 +118,7 @@ namespace osu.Game.Rulesets.UI { d.OnNestedDrawableCreated += onNewDrawableHitObject; - OnNewDrawableHitObject?.Invoke(d); + OnNewDrawableHitObject(d); Debug.Assert(!d.HasInitialized); d.HasInitialized = true; @@ -183,6 +173,17 @@ namespace osu.Game.Rulesets.UI { } + /// + /// Invoked before a new is added to this . + /// It is invoked only once even the drawable is pooled and used multiple times for different s. + /// + /// + /// This is also invoked for nested s. + /// + protected virtual void OnNewDrawableHitObject(DrawableHitObject drawableHitObject) + { + } + /// /// The cursor currently being used by this . May be null if no cursor is provided. /// From 99a95790c3ac901b83d34c67f3dc485920437b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Nov 2020 14:36:59 +0100 Subject: [PATCH 125/394] Resolve test failure --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 44a2056732..192d65f3fe 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -326,7 +326,7 @@ namespace osu.Game.Tests.Visual.Ranking public HotkeyRetryOverlay RetryOverlay; public UnrankedSoloResultsScreen(ScoreInfo score) - : base(score, false) + : base(score, true) { Score.Beatmap.OnlineBeatmapID = 0; Score.Beatmap.Status = BeatmapSetOnlineStatus.Pending; From 875f986979b22832eeff88a96bf0448241325730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Nov 2020 14:38:38 +0100 Subject: [PATCH 126/394] Remove default from base ResultsScreen too --- osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs | 2 +- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 9ef9649f77..5323f58a66 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -318,7 +318,7 @@ namespace osu.Game.Tests.Visual.Background private class FadeAccessibleResults : ResultsScreen { public FadeAccessibleResults(ScoreInfo score) - : base(score) + : base(score, true) { } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 192d65f3fe..b2be7cdf88 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -256,7 +256,7 @@ namespace osu.Game.Tests.Visual.Ranking public HotkeyRetryOverlay RetryOverlay; public TestResultsScreen(ScoreInfo score) - : base(score) + : base(score, true) { } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index f8bdf0140c..626db9baa6 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Ranking private readonly bool allowRetry; - protected ResultsScreen(ScoreInfo score, bool allowRetry = true) + protected ResultsScreen(ScoreInfo score, bool allowRetry) { Score = score; this.allowRetry = allowRetry; From 9f997db9587ec142977b7333c1e0176f4b994a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Nov 2020 17:55:56 +0100 Subject: [PATCH 127/394] Rewind judgement transforms before clearing --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 3063656aaf..cd6c001172 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -149,6 +149,7 @@ namespace osu.Game.Rulesets.Judgements private void runAnimation() { + ApplyTransformsAt(double.MinValue, true); ClearTransforms(true); LifetimeStart = Result.TimeAbsolute; From 81d0b42930426bddb6793411ae9dce234fbd9c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Nov 2020 19:51:27 +0100 Subject: [PATCH 128/394] Add failing test case --- .../Skinning/LegacySkinAnimationTest.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs new file mode 100644 index 0000000000..509e2fe8ca --- /dev/null +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs @@ -0,0 +1,79 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Audio; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.NonVisual.Skinning +{ + [HeadlessTest] + public class LegacySkinAnimationTest : OsuTestScene + { + private const string animation_name = "animation"; + private const int frame_count = 6; + + [Cached(typeof(IAnimationTimeReference))] + private TestAnimationTimeReference animationTimeReference = new TestAnimationTimeReference(); + + private TextureAnimation animation; + + [Test] + public void TestAnimationTimeReferenceChange() + { + ISkin skin = new TestSkin(); + + AddStep("get animation", () => Add(animation = (TextureAnimation)skin.GetAnimation(animation_name, true, false))); + AddAssert("frame count correct", () => animation.FrameCount == frame_count); + assertPlaybackPosition(0); + + AddStep("set start time to 1000", () => animationTimeReference.AnimationStartTime = 1000); + assertPlaybackPosition(-1000); + + AddStep("set current time to 500", () => animationTimeReference.ManualClock.CurrentTime = 500); + assertPlaybackPosition(-500); + } + + private void assertPlaybackPosition(double expectedPosition) + => AddAssert($"playback position is {expectedPosition}", () => animation.PlaybackPosition == expectedPosition); + + private class TestSkin : ISkin + { + private static readonly string[] lookup_names = Enumerable.Range(0, frame_count).Select(frame => $"{animation_name}-{frame}").ToArray(); + + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) + { + return lookup_names.Contains(componentName) ? Texture.WhitePixel : null; + } + + public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException(); + public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException(); + public IBindable GetConfig(TLookup lookup) => throw new NotSupportedException(); + } + + private class TestAnimationTimeReference : IAnimationTimeReference + { + public ManualClock ManualClock { get; } + public IFrameBasedClock Clock { get; } + public double AnimationStartTime { get; set; } + + public TestAnimationTimeReference() + { + ManualClock = new ManualClock(); + Clock = new FramedClock(ManualClock); + } + } + } +} From 240c1b0aef9b24162acb488bece8b5bc988c2fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Nov 2020 20:06:30 +0100 Subject: [PATCH 129/394] Add support for changing animation start time after load --- .../Objects/Drawables/Connections/FollowPoint.cs | 4 +++- .../Connections/FollowPointConnection.cs | 2 +- .../NonVisual/Skinning/LegacySkinAnimationTest.cs | 5 +++-- osu.Game/Skinning/IAnimationTimeReference.cs | 3 ++- osu.Game/Skinning/LegacySkinExtensions.cs | 15 ++++++++++++++- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 3e2ab65bb2..be274131c0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osuTK; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; @@ -47,6 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections }); } - public double AnimationStartTime { get; set; } + public Bindable AnimationStartTime { get; } = new BindableDouble(); + IBindable IAnimationTimeReference.AnimationStartTime => AnimationStartTime; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 700d96eff3..6e7b1050cb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections fp.Alpha = 0; fp.Scale = new Vector2(1.5f * end.Scale); - fp.AnimationStartTime = fadeInTime; + fp.AnimationStartTime.Value = fadeInTime; using (fp.BeginAbsoluteSequence(fadeInTime)) { diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs index 509e2fe8ca..f05938cb39 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.NonVisual.Skinning AddAssert("frame count correct", () => animation.FrameCount == frame_count); assertPlaybackPosition(0); - AddStep("set start time to 1000", () => animationTimeReference.AnimationStartTime = 1000); + AddStep("set start time to 1000", () => animationTimeReference.AnimationStartTime.Value = 1000); assertPlaybackPosition(-1000); AddStep("set current time to 500", () => animationTimeReference.ManualClock.CurrentTime = 500); @@ -67,7 +67,8 @@ namespace osu.Game.Tests.NonVisual.Skinning { public ManualClock ManualClock { get; } public IFrameBasedClock Clock { get; } - public double AnimationStartTime { get; set; } + public Bindable AnimationStartTime { get; } = new BindableDouble(); + IBindable IAnimationTimeReference.AnimationStartTime => AnimationStartTime; public TestAnimationTimeReference() { diff --git a/osu.Game/Skinning/IAnimationTimeReference.cs b/osu.Game/Skinning/IAnimationTimeReference.cs index 7e52bb8176..c987372f4b 100644 --- a/osu.Game/Skinning/IAnimationTimeReference.cs +++ b/osu.Game/Skinning/IAnimationTimeReference.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Timing; @@ -25,6 +26,6 @@ namespace osu.Game.Skinning /// /// The time which animations should be started from, relative to . /// - double AnimationStartTime { get; } + IBindable AnimationStartTime { get; } } } diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 0ee02a2442..b57852847c 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.OpenGL.Textures; @@ -70,6 +71,8 @@ namespace osu.Game.Skinning [Resolved(canBeNull: true)] private IAnimationTimeReference timeReference { get; set; } + private readonly Bindable animationStartTime = new BindableDouble(); + public SkinnableTextureAnimation(bool startAtCurrentTime = true) : base(startAtCurrentTime) { @@ -82,8 +85,18 @@ namespace osu.Game.Skinning if (timeReference != null) { Clock = timeReference.Clock; - PlaybackPosition = timeReference.Clock.CurrentTime - timeReference.AnimationStartTime; + ((IBindable)animationStartTime).BindTo(timeReference.AnimationStartTime); } + + animationStartTime.BindValueChanged(_ => updatePlaybackPosition(), true); + } + + private void updatePlaybackPosition() + { + if (timeReference == null) + return; + + PlaybackPosition = timeReference.Clock.CurrentTime - timeReference.AnimationStartTime.Value; } } From 11c3ccfcaa4a4a2adb130e3bbbcda97a0ead8c78 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 21 Nov 2020 22:49:56 +0300 Subject: [PATCH 130/394] Move rulesets property to PaginatedProfileSubsection --- .../Profile/Sections/PaginatedProfileSubsection.cs | 4 ++++ osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 1f897d704a..3e6b20bf14 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; namespace osu.Game.Overlays.Profile.Sections { @@ -20,6 +21,9 @@ namespace osu.Game.Overlays.Profile.Sections [Resolved] private IAPIProvider api { get; set; } + [Resolved] + protected RulesetStore Rulesets { get; private set; } + protected int VisiblePages; protected int ItemsPerPage; diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs index 751b35e342..6f68804827 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; using osu.Game.Users; using JetBrains.Annotations; @@ -17,8 +16,6 @@ namespace osu.Game.Overlays.Profile.Sections { protected readonly Bindable User = new Bindable(); - protected RulesetStore Rulesets { get; private set; } - protected OsuSpriteText Missing { get; private set; } private readonly string headerText; @@ -36,7 +33,7 @@ namespace osu.Game.Overlays.Profile.Sections } [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) + private void load() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -56,8 +53,6 @@ namespace osu.Game.Overlays.Profile.Sections Alpha = 0, }, }; - - Rulesets = rulesets; } protected override void LoadComplete() From dbfc839df379c65b09279704036347384ce6ef90 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 21 Nov 2020 23:03:54 +0300 Subject: [PATCH 131/394] Move missing text to PaginatedProfileSubsection --- .../Sections/PaginatedProfileSubsection.cs | 19 +++++++++++++++---- .../Profile/Sections/ProfileSubsection.cs | 16 ++-------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 3e6b20bf14..b5ae949105 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -13,6 +13,8 @@ using System.Linq; using System.Threading; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; namespace osu.Game.Overlays.Profile.Sections { @@ -33,10 +35,13 @@ namespace osu.Game.Overlays.Profile.Sections private CancellationTokenSource loadCancellation; private ShowMoreButton moreButton; + private OsuSpriteText missing; + private readonly string missingText; protected PaginatedProfileSubsection(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) - : base(user, headerText, missingText, counterVisibilityState) + : base(user, headerText, counterVisibilityState) { + this.missingText = missingText; } protected override Drawable CreateContent() => new FillFlowContainer @@ -59,6 +64,12 @@ namespace osu.Game.Overlays.Profile.Sections Alpha = 0, Margin = new MarginPadding { Top = 10 }, Action = showMore, + }, + missing = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 15), + Text = missingText, + Alpha = 0, } } }; @@ -97,15 +108,15 @@ namespace osu.Game.Overlays.Profile.Sections moreButton.Hide(); moreButton.IsLoading = false; - if (!string.IsNullOrEmpty(Missing.Text)) - Missing.Show(); + if (!string.IsNullOrEmpty(missingText)) + missing.Show(); return; } LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null), drawables => { - Missing.Hide(); + missing.Hide(); moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); moreButton.IsLoading = false; diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs index 6f68804827..0743823113 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -5,8 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Users; using JetBrains.Annotations; @@ -16,18 +14,14 @@ namespace osu.Game.Overlays.Profile.Sections { protected readonly Bindable User = new Bindable(); - protected OsuSpriteText Missing { get; private set; } - private readonly string headerText; - private readonly string missingText; private readonly CounterVisibilityState counterVisibilityState; private ProfileSubsectionHeader header; - protected ProfileSubsection(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + protected ProfileSubsection(Bindable user, string headerText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) { this.headerText = headerText; - this.missingText = missingText; this.counterVisibilityState = counterVisibilityState; User.BindTo(user); } @@ -45,13 +39,7 @@ namespace osu.Game.Overlays.Profile.Sections { Alpha = string.IsNullOrEmpty(headerText) ? 0 : 1 }, - CreateContent(), - Missing = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 15), - Text = missingText, - Alpha = 0, - }, + CreateContent() }; } From fe4c6220418b1f8560f6e6bcdc9c8994e9912efd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 21 Nov 2020 23:13:46 +0300 Subject: [PATCH 132/394] Make OnUserChanged private --- .../Profile/Sections/PaginatedProfileSubsection.cs | 8 +++++++- .../Overlays/Profile/Sections/ProfileSubsection.cs | 10 ---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index b5ae949105..51e5622f68 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -74,7 +74,13 @@ namespace osu.Game.Overlays.Profile.Sections } }; - protected override void OnUserChanged(ValueChangedEvent e) + protected override void LoadComplete() + { + base.LoadComplete(); + User.BindValueChanged(onUserChanged, true); + } + + private void onUserChanged(ValueChangedEvent e) { loadCancellation?.Cancel(); retrievalRequest?.Cancel(); diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs index 0743823113..3e331f85e9 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -43,19 +43,9 @@ namespace osu.Game.Overlays.Profile.Sections }; } - protected override void LoadComplete() - { - base.LoadComplete(); - User.BindValueChanged(OnUserChanged, true); - } - [NotNull] protected abstract Drawable CreateContent(); - protected virtual void OnUserChanged(ValueChangedEvent e) - { - } - protected void SetCount(int value) => header.Current.Value = value; } } From d4b56aac8403565e2c5f5629c0abe550f76500ad Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 22 Nov 2020 02:17:54 +0300 Subject: [PATCH 133/394] Add missing whitespace --- osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 75c3ee8290..51e5622f68 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections protected int VisiblePages; protected int ItemsPerPage; + protected FillFlowContainer ItemsContainer { get; private set; } private APIRequest> retrievalRequest; From 3cb1d0466734b0b2c07554f14a4c6f7e59f242c9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 22 Nov 2020 02:25:12 +0300 Subject: [PATCH 134/394] Move dates fill into it's own method --- .../Historical/ChartProfileSubsection.cs | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs index 9413d241fa..783ecec190 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs @@ -44,32 +44,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical if (values?.Length > 1) { - // Fill dates with 0 count - - var newValues = new List { values[0] }; - var newLast = values[0]; - - for (int i = 1; i < values.Length; i++) - { - while (hasMissingDates(newLast, values[i])) - { - newValues.Add(newLast = new UserHistoryCount - { - Count = 0, - Date = newLast.Date.AddMonths(1) - }); - } - - newValues.Add(newLast = values[i]); - } - - static bool hasMissingDates(UserHistoryCount prev, UserHistoryCount current) - { - var possibleCurrent = prev.Date.AddMonths(1); - return possibleCurrent != current.Date; - } - - chart.Values = newValues.ToArray(); + chart.Values = fillZeroValues(values); Show(); return; } @@ -77,6 +52,34 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Hide(); } + private UserHistoryCount[] fillZeroValues(UserHistoryCount[] values) + { + var newValues = new List { values[0] }; + var newLast = values[0]; + + for (int i = 1; i < values.Length; i++) + { + while (hasMissingDates(newLast, values[i])) + { + newValues.Add(newLast = new UserHistoryCount + { + Count = 0, + Date = newLast.Date.AddMonths(1) + }); + } + + newValues.Add(newLast = values[i]); + } + + return newValues.ToArray(); + + static bool hasMissingDates(UserHistoryCount prev, UserHistoryCount current) + { + var possibleCurrent = prev.Date.AddMonths(1); + return possibleCurrent != current.Date; + } + } + protected abstract UserHistoryCount[] GetValues(User user); } } From 453f0ba675de81682ba13a72225e9ad0211b5e2e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 22 Nov 2020 02:34:29 +0300 Subject: [PATCH 135/394] Make tick lines thicker --- .../Profile/Sections/Historical/ProfileLineChart.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 5a9c42d7e0..c658ac1aa7 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Framework.Graphics.Shapes; using static osu.Game.Users.User; +using osuTK; namespace osu.Game.Overlays.Profile.Sections.Historical { @@ -140,7 +141,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.X, RelativePositionAxes = Axes.Y, - Height = 1, + Height = 0.1f, + EdgeSmoothness = Vector2.One, Y = y }); @@ -180,7 +182,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.Y, RelativePositionAxes = Axes.X, - Width = 1, + Width = 0.1f, + EdgeSmoothness = Vector2.One, X = x }); From 6e581902cdd49c05b513518df1b4f84bfc5b71b2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 22 Nov 2020 03:11:38 +0300 Subject: [PATCH 136/394] Simplify column ticks creation --- .../Sections/Historical/ProfileLineChart.cs | 109 ++++++++++-------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index c658ac1aa7..31c14d3b19 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -123,28 +123,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical while (rollingRow <= max) { var y = -Interpolation.ValueAt(rollingRow, 0, 1f, min, max); - - rowTicksContainer.Add(new TickText - { - Anchor = Anchor.BottomRight, - Origin = Anchor.CentreRight, - RelativePositionAxes = Axes.Y, - Margin = new MarginPadding { Right = 3 }, - Text = rollingRow.ToString("N0"), - Font = OsuFont.GetFont(size: 12), - Y = y - }); - - rowLinesContainer.Add(new TickLine - { - Anchor = Anchor.BottomRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.X, - RelativePositionAxes = Axes.Y, - Height = 0.1f, - EdgeSmoothness = Vector2.One, - Y = y - }); + addRowTick(y, (long)rollingRow); rollingRow += niceTick; } @@ -155,42 +134,70 @@ namespace osu.Game.Overlays.Profile.Sections.Historical columnTicksContainer.Clear(); columnLinesContainer.Clear(); - var min = values.Select(v => v.Date).Min().ToOADate(); - var max = values.Select(v => v.Date).Max().ToOADate(); + var totalMonths = values.Length - 1; - var niceRange = niceNumber(max - min, false); - var niceTick = niceNumber(niceRange / (Math.Min(values.Length, 15) - 1), true); + int monthsPerTick = 1; - double rollingRow = min; + if (totalMonths >= 45) + monthsPerTick = 3; + else if (totalMonths >= 20) + monthsPerTick = 2; - while (rollingRow <= max) + for (int i = 0; i < totalMonths; i += monthsPerTick) { - var x = Interpolation.ValueAt(rollingRow, 0, 1f, min, max); - - columnTicksContainer.Add(new TickText - { - Origin = Anchor.CentreLeft, - RelativePositionAxes = Axes.X, - Text = DateTime.FromOADate(rollingRow).ToString("MMM yyyy"), - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Rotation = 45, - X = x - }); - - columnLinesContainer.Add(new TickLine - { - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - RelativePositionAxes = Axes.X, - Width = 0.1f, - EdgeSmoothness = Vector2.One, - X = x - }); - - rollingRow += niceTick; + var x = (float)i / totalMonths; + addColumnTick(x, values[i].Date); } } + private void addRowTick(float y, long value) + { + rowTicksContainer.Add(new TickText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.CentreRight, + RelativePositionAxes = Axes.Y, + Margin = new MarginPadding { Right = 3 }, + Text = value.ToString("N0"), + Font = OsuFont.GetFont(size: 12), + Y = y + }); + + rowLinesContainer.Add(new TickLine + { + Anchor = Anchor.BottomRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.X, + RelativePositionAxes = Axes.Y, + Height = 0.1f, + EdgeSmoothness = Vector2.One, + Y = y + }); + } + + private void addColumnTick(float x, DateTime value) + { + columnTicksContainer.Add(new TickText + { + Origin = Anchor.CentreLeft, + RelativePositionAxes = Axes.X, + Text = value.ToString("MMM yyyy"), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Rotation = 45, + X = x + }); + + columnLinesContainer.Add(new TickLine + { + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + RelativePositionAxes = Axes.X, + Width = 0.1f, + EdgeSmoothness = Vector2.One, + X = x + }); + } + private double niceNumber(double value, bool round) { var exponent = Math.Floor(Math.Log10(value)); From e6c116f0ab35b181e1d353a0640ec1cd1bcb23c5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 22 Nov 2020 03:49:00 +0300 Subject: [PATCH 137/394] Rework horizontal ticks creation --- .../Sections/Historical/ProfileLineChart.cs | 73 ++++++++++--------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 31c14d3b19..770da21657 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -115,17 +115,19 @@ namespace osu.Game.Overlays.Profile.Sections.Historical var min = values.Select(v => v.Count).Min(); var max = values.Select(v => v.Count).Max(); - var niceRange = niceNumber(max - min, false); - var niceTick = niceNumber(niceRange / (6 - 1), true); + var tick = getTick(getRange(max - min), 6); - double rollingRow = min; + double rollingRow = 0; while (rollingRow <= max) { - var y = -Interpolation.ValueAt(rollingRow, 0, 1f, min, max); - addRowTick(y, (long)rollingRow); + if (rollingRow >= min) + { + var y = -Interpolation.ValueAt(rollingRow, 0, 1f, min, max); + addRowTick(y, (long)rollingRow); + } - rollingRow += niceTick; + rollingRow += tick; } } @@ -138,10 +140,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical int monthsPerTick = 1; - if (totalMonths >= 45) - monthsPerTick = 3; - else if (totalMonths >= 20) - monthsPerTick = 2; + if (totalMonths > 20) + monthsPerTick = totalMonths / 10; for (int i = 0; i < totalMonths; i += monthsPerTick) { @@ -198,37 +198,44 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }); } - private double niceNumber(double value, bool round) + private long getRange(double initialRange) { + var exponent = Math.Floor(Math.Log10(initialRange)); + var fraction = initialRange / Math.Pow(10, exponent); + + double niceFraction; + + if (fraction <= 1.0) + niceFraction = 1.0; + else if (fraction <= 2.0) + niceFraction = 2.0; + else if (fraction <= 5.0) + niceFraction = 5.0; + else + niceFraction = 10.0; + + return (long)(niceFraction * Math.Pow(10, exponent)); + } + + private long getTick(long range, int maxTicksCount) + { + var value = range / (maxTicksCount - 1); + var exponent = Math.Floor(Math.Log10(value)); var fraction = value / Math.Pow(10, exponent); double niceFraction; - if (round) - { - if (fraction < 1.5) - niceFraction = 1.0; - else if (fraction < 3) - niceFraction = 2.0; - else if (fraction < 7) - niceFraction = 5.0; - else - niceFraction = 10.0; - } + if (fraction < 1.5) + niceFraction = 1.0; + else if (fraction < 3) + niceFraction = 2.0; + else if (fraction < 7) + niceFraction = 5.0; else - { - if (fraction <= 1.0) - niceFraction = 1.0; - else if (fraction <= 2.0) - niceFraction = 2.0; - else if (fraction <= 5.0) - niceFraction = 5.0; - else - niceFraction = 10.0; - } + niceFraction = 10.0; - return niceFraction * Math.Pow(10, exponent); + return (long)(niceFraction * Math.Pow(10, exponent)); } private class TickText : OsuSpriteText From f07f8089d69062f44077349945a7680a2ea20c21 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 22 Nov 2020 03:58:56 +0300 Subject: [PATCH 138/394] Adjust monthsPerTick value --- .../Profile/Sections/Historical/ProfileLineChart.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 770da21657..932005a52f 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -140,8 +140,12 @@ namespace osu.Game.Overlays.Profile.Sections.Historical int monthsPerTick = 1; - if (totalMonths > 20) - monthsPerTick = totalMonths / 10; + if (totalMonths > 80) + monthsPerTick = 12; + else if (totalMonths >= 45) + monthsPerTick = 3; + else if (totalMonths > 20) + monthsPerTick = 2; for (int i = 0; i < totalMonths; i += monthsPerTick) { From 48871329471daf2a5c531460f5490250492529cb Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 22 Nov 2020 04:28:17 +0300 Subject: [PATCH 139/394] Adjustments for edge cases support --- .../Sections/Historical/ProfileLineChart.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 932005a52f..688930d76a 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -117,6 +117,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical var tick = getTick(getRange(max - min), 6); + bool tickIsDecimal = tick < 1.0; + double rollingRow = 0; while (rollingRow <= max) @@ -124,7 +126,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical if (rollingRow >= min) { var y = -Interpolation.ValueAt(rollingRow, 0, 1f, min, max); - addRowTick(y, (long)rollingRow); + addRowTick(y, rollingRow, tickIsDecimal); } rollingRow += tick; @@ -136,7 +138,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical columnTicksContainer.Clear(); columnLinesContainer.Clear(); - var totalMonths = values.Length - 1; + var totalMonths = values.Length; int monthsPerTick = 1; @@ -149,12 +151,12 @@ namespace osu.Game.Overlays.Profile.Sections.Historical for (int i = 0; i < totalMonths; i += monthsPerTick) { - var x = (float)i / totalMonths; + var x = (float)i / (totalMonths - 1); addColumnTick(x, values[i].Date); } } - private void addRowTick(float y, long value) + private void addRowTick(float y, double value, bool tickIsDecimal) { rowTicksContainer.Add(new TickText { @@ -162,7 +164,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Origin = Anchor.CentreRight, RelativePositionAxes = Axes.Y, Margin = new MarginPadding { Right = 3 }, - Text = value.ToString("N0"), + Text = tickIsDecimal ? value.ToString("F1") : value.ToString("N0"), Font = OsuFont.GetFont(size: 12), Y = y }); @@ -202,7 +204,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }); } - private long getRange(double initialRange) + private double getRange(double initialRange) { var exponent = Math.Floor(Math.Log10(initialRange)); var fraction = initialRange / Math.Pow(10, exponent); @@ -218,10 +220,10 @@ namespace osu.Game.Overlays.Profile.Sections.Historical else niceFraction = 10.0; - return (long)(niceFraction * Math.Pow(10, exponent)); + return niceFraction * Math.Pow(10, exponent); } - private long getTick(long range, int maxTicksCount) + private double getTick(double range, int maxTicksCount) { var value = range / (maxTicksCount - 1); @@ -239,7 +241,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical else niceFraction = 10.0; - return (long)(niceFraction * Math.Pow(10, exponent)); + return niceFraction * Math.Pow(10, exponent); } private class TickText : OsuSpriteText From b745fb681a8bfba02e01317f684fa1a3a8b699f0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 22 Nov 2020 04:40:55 +0300 Subject: [PATCH 140/394] Fix incorrect static using placement --- .../Overlays/Profile/Sections/Historical/ProfileLineChart.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 688930d76a..1ce14fa245 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -11,8 +11,8 @@ using osu.Framework.Utils; using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Framework.Graphics.Shapes; -using static osu.Game.Users.User; using osuTK; +using static osu.Game.Users.User; namespace osu.Game.Overlays.Profile.Sections.Historical { From 5247ebaf53d32a830dc652915d2f6641afe60399 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 22 Nov 2020 18:30:51 +0900 Subject: [PATCH 141/394] Restore accidently removed comment --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index dafde0b927..5fdb79cbbd 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -34,6 +34,10 @@ namespace osu.Game.Rulesets.Osu.Edit d.ApplyCustomUpdateState += updateState; } + /// + /// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay. + /// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points. + /// private const double editor_hit_object_fade_out_extension = 700; private void updateState(DrawableHitObject hitObject, ArmedState state) From c506b438bf465947fe0cff2f9bb25044f6bd35f9 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 22 Nov 2020 18:36:10 +0900 Subject: [PATCH 142/394] Remove more code and make some methods private --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 4 ++-- .../Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/DrawableSlider.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 5 +---- .../IDrawableHitObjectWithProxiedApproach.cs | 12 ------------ 5 files changed, 5 insertions(+), 20 deletions(-) delete mode 100644 osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 7d8f18ee0b..abbdeacd9a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -62,10 +62,10 @@ namespace osu.Game.Rulesets.Catch.UI protected override void OnNewDrawableHitObject(DrawableHitObject d) { - ((DrawableCatchHitObject)d).CheckPosition = CheckIfWeCanCatch; + ((DrawableCatchHitObject)d).CheckPosition = checkIfWeCanCatch; } - public bool CheckIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj); + private bool checkIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj); private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) => CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index d1ceca6d8f..abb51ae420 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach + public class DrawableHitCircle : DrawableOsuHitObject { public OsuAction? HitAction => HitArea.HitAction; protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 14c494d909..6340367593 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -19,7 +19,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach + public class DrawableSlider : DrawableOsuHitObject { public new Slider HitObject => (Slider)base.HitObject; diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 93302c6046..8ff752952c 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -26,8 +26,6 @@ namespace osu.Game.Rulesets.Osu.UI { public class OsuPlayfield : Playfield { - public readonly Func CheckHittable; - private readonly PlayfieldBorder playfieldBorder; private readonly ProxyContainer approachCircles; private readonly ProxyContainer spinnerProxies; @@ -57,7 +55,6 @@ namespace osu.Game.Rulesets.Osu.UI }; hitPolicy = new OrderedHitPolicy(HitObjectContainer); - CheckHittable = hitPolicy.IsHittable; var hitWindows = new OsuHitWindows(); @@ -71,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override void OnNewDrawableHitObject(DrawableHitObject drawable) { - ((DrawableOsuHitObject)drawable).CheckHittable = CheckHittable; + ((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.IsHittable; Debug.Assert(!drawable.IsLoaded, $"Already loaded {nameof(DrawableHitObject)} is added to {nameof(OsuPlayfield)}"); drawable.OnLoadComplete += onDrawableHitObjectLoaded; diff --git a/osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs b/osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs deleted file mode 100644 index 8f4c95c634..0000000000 --- a/osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; - -namespace osu.Game.Rulesets.Objects.Drawables -{ - public interface IDrawableHitObjectWithProxiedApproach - { - Drawable ProxiedLayer { get; } - } -} From 666112cb5a7faa7060c8131f9a20e87a4f24dd07 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 22 Nov 2020 18:47:35 +0900 Subject: [PATCH 143/394] Address @bdach's minor suggestions --- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- osu.Game/Rulesets/UI/Playfield.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 84b2dd7957..6ed6c6412e 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Whether the initialization logic in has applied. /// - internal bool HasInitialized; + internal bool IsInitialized; /// /// Creates a new . diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index fcdd5ff53d..2f589f4ce9 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -120,8 +120,8 @@ namespace osu.Game.Rulesets.UI OnNewDrawableHitObject(d); - Debug.Assert(!d.HasInitialized); - d.HasInitialized = true; + Debug.Assert(!d.IsInitialized); + d.IsInitialized = true; } /// @@ -135,8 +135,8 @@ namespace osu.Game.Rulesets.UI /// The DrawableHitObject to add. public virtual void Add(DrawableHitObject h) { - if (h.HasInitialized) - throw new InvalidOperationException($"{nameof(Playfield.Add)} doesn't support {nameof(DrawableHitObject)} reuse. Use pooling instead."); + if (h.IsInitialized) + throw new InvalidOperationException($"{nameof(Add)} doesn't support {nameof(DrawableHitObject)} reuse. Use pooling instead."); onNewDrawableHitObject(h); @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.UI /// /// Invoked before a new is added to this . - /// It is invoked only once even the drawable is pooled and used multiple times for different s. + /// It is invoked only once even if the drawable is pooled and used multiple times for different s. /// /// /// This is also invoked for nested s. @@ -348,7 +348,7 @@ namespace osu.Game.Rulesets.UI { var dho = (DrawableHitObject)d; - if (!dho.HasInitialized) + if (!dho.IsInitialized) { onNewDrawableHitObject(dho); From 1c31a4a6b6513a97ad91ae47a094be35aea41f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Nov 2020 13:11:22 +0100 Subject: [PATCH 144/394] Expose animation start time as mutable in interface --- .../Objects/Drawables/Connections/FollowPoint.cs | 1 - osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs | 1 - osu.Game/Skinning/IAnimationTimeReference.cs | 2 +- osu.Game/Skinning/LegacySkinExtensions.cs | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index be274131c0..b989500066 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -49,6 +49,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } public Bindable AnimationStartTime { get; } = new BindableDouble(); - IBindable IAnimationTimeReference.AnimationStartTime => AnimationStartTime; } } diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs index f05938cb39..a5c937119e 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs @@ -68,7 +68,6 @@ namespace osu.Game.Tests.NonVisual.Skinning public ManualClock ManualClock { get; } public IFrameBasedClock Clock { get; } public Bindable AnimationStartTime { get; } = new BindableDouble(); - IBindable IAnimationTimeReference.AnimationStartTime => AnimationStartTime; public TestAnimationTimeReference() { diff --git a/osu.Game/Skinning/IAnimationTimeReference.cs b/osu.Game/Skinning/IAnimationTimeReference.cs index c987372f4b..f627379a57 100644 --- a/osu.Game/Skinning/IAnimationTimeReference.cs +++ b/osu.Game/Skinning/IAnimationTimeReference.cs @@ -26,6 +26,6 @@ namespace osu.Game.Skinning /// /// The time which animations should be started from, relative to . /// - IBindable AnimationStartTime { get; } + Bindable AnimationStartTime { get; } } } diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index b57852847c..a7c084998d 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -85,7 +85,7 @@ namespace osu.Game.Skinning if (timeReference != null) { Clock = timeReference.Clock; - ((IBindable)animationStartTime).BindTo(timeReference.AnimationStartTime); + animationStartTime.BindTo(timeReference.AnimationStartTime); } animationStartTime.BindValueChanged(_ => updatePlaybackPosition(), true); From cc33b0f2c633634e4b0a21f17560ee1b9cc21138 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 22 Nov 2020 16:53:27 +0100 Subject: [PATCH 145/394] Apply MenuGlow to Visualisation Colour instead of AccentColour --- osu.Game/Screens/Menu/LogoVisualisation.cs | 3 ++- osu.Game/Screens/Menu/MenuLogoVisualisation.cs | 6 +++--- osu.Game/Screens/Menu/OsuLogo.cs | 1 - 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index ebbb19636c..1f7ea3df31 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -20,6 +20,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Utils; +using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Screens.Menu { @@ -67,7 +68,7 @@ namespace osu.Game.Screens.Menu private int indexOffset; - public Color4 AccentColour { get; set; } + public Color4 AccentColour { get; set; } = Color4.White.Opacity(.2f); /// /// The relative movement of bars based on input amplification. Defaults to 1. diff --git a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs index 5eb3f1efa0..bc7c25a26f 100644 --- a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs +++ b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs @@ -28,12 +28,12 @@ namespace osu.Game.Screens.Menu private void updateColour() { - Color4 defaultColour = Color4.White.Opacity(0.2f); + Color4 defaultColour = Color4.White; if (user.Value?.IsSupporter ?? false) - AccentColour = skin.Value.GetConfig(GlobalSkinColours.MenuGlow)?.Value ?? defaultColour; + Colour = skin.Value.GetConfig(GlobalSkinColours.MenuGlow)?.Value ?? defaultColour; else - AccentColour = defaultColour; + Colour = defaultColour; } } } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 4515ee8ed0..8fa9d6e933 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -144,7 +144,6 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Alpha = 0.5f, Size = new Vector2(0.96f) }, new Container From b468f061f176ce4103f4f11680a91957b0d791e2 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 22 Nov 2020 17:20:00 +0100 Subject: [PATCH 146/394] Remove unused using --- osu.Game/Screens/Menu/MenuLogoVisualisation.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs index bc7c25a26f..31a3c2dffd 100644 --- a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs +++ b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs @@ -7,7 +7,6 @@ using osu.Game.Online.API; using osu.Game.Users; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Screens.Menu { From 1551402a8d2e812b87e270892a2252c21a3bc8ca Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 22 Nov 2020 17:33:25 +0100 Subject: [PATCH 147/394] Change IntroWelcome visualiser to use Colour instead of AccentColour --- osu.Game/Screens/Menu/IntroWelcome.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index e81646456f..abb83f894a 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -113,8 +113,7 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Alpha = 0.5f, - AccentColour = Color4.DarkBlue, + Colour = Color4.DarkBlue, Size = new Vector2(0.96f) }, new Circle From 458016d17da024f0165ce977b0472eee7b7edd0b Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 22 Nov 2020 17:34:56 +0100 Subject: [PATCH 148/394] Remove AccentColour member variable from LogoVisualisation --- osu.Game/Screens/Menu/LogoVisualisation.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 1f7ea3df31..5eb14a8dc1 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; -using osu.Game.Graphics; using System; using System.Collections.Generic; using JetBrains.Annotations; @@ -27,7 +26,7 @@ namespace osu.Game.Screens.Menu /// /// A visualiser that reacts to music coming from beatmaps. /// - public class LogoVisualisation : Drawable, IHasAccentColour + public class LogoVisualisation : Drawable { private readonly IBindable beatmap = new Bindable(); @@ -68,8 +67,6 @@ namespace osu.Game.Screens.Menu private int indexOffset; - public Color4 AccentColour { get; set; } = Color4.White.Opacity(.2f); - /// /// The relative movement of bars based on input amplification. Defaults to 1. /// @@ -177,7 +174,7 @@ namespace osu.Game.Screens.Menu // Assuming the logo is a circle, we don't need a second dimension. private float size; - private Color4 colour; + private Color4 colour = Color4.White.Opacity(.2f); private float[] audioData; private readonly QuadBatch vertexBatch = new QuadBatch(100, 10); @@ -194,7 +191,6 @@ namespace osu.Game.Screens.Menu shader = Source.shader; texture = Source.texture; size = Source.DrawSize.X; - colour = Source.AccentColour; audioData = Source.frequencyAmplitudes; } From ba7ce4c93363cd19791527d21f9d7de01129e8f3 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 22 Nov 2020 18:37:49 +0100 Subject: [PATCH 149/394] Make colour readonly --- osu.Game/Screens/Menu/LogoVisualisation.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 5eb14a8dc1..1d62305059 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -174,7 +174,8 @@ namespace osu.Game.Screens.Menu // Assuming the logo is a circle, we don't need a second dimension. private float size; - private Color4 colour = Color4.White.Opacity(.2f); + private readonly Color4 colour = Color4.White.Opacity(.2f); + private float[] audioData; private readonly QuadBatch vertexBatch = new QuadBatch(100, 10); From 61078e9ae1649426efb3cd43c2b0a51ef1c10a09 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 22 Nov 2020 18:59:16 +0100 Subject: [PATCH 150/394] Use 0.2f instead of .2f --- osu.Game/Screens/Menu/LogoVisualisation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 1d62305059..f6e011509c 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -174,7 +174,7 @@ namespace osu.Game.Screens.Menu // Assuming the logo is a circle, we don't need a second dimension. private float size; - private readonly Color4 colour = Color4.White.Opacity(.2f); + private readonly Color4 colour = Color4.White.Opacity(0.2f); private float[] audioData; From 2ae5a95d77d822cad544dc654c12751472b37ca6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Nov 2020 13:35:02 +0900 Subject: [PATCH 151/394] Change opacity value to match master implementation --- osu.Game/Screens/Menu/LogoVisualisation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index f6e011509c..c96b08902c 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -174,7 +174,7 @@ namespace osu.Game.Screens.Menu // Assuming the logo is a circle, we don't need a second dimension. private float size; - private readonly Color4 colour = Color4.White.Opacity(0.2f); + private static readonly Color4 transparent_white = Color4.White.Opacity(0.1f); private float[] audioData; @@ -204,7 +204,7 @@ namespace osu.Game.Screens.Menu Vector2 inflation = DrawInfo.MatrixInverse.ExtractScale().Xy; ColourInfo colourInfo = DrawColourInfo.Colour; - colourInfo.ApplyChild(colour); + colourInfo.ApplyChild(transparent_white); if (audioData != null) { From ae609b9d48c50ae6f7ad291f191ee76309fda123 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Nov 2020 13:35:13 +0900 Subject: [PATCH 152/394] Remove unnecessary local variable --- osu.Game/Screens/Menu/MenuLogoVisualisation.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs index 31a3c2dffd..92add458f9 100644 --- a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs +++ b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs @@ -27,12 +27,10 @@ namespace osu.Game.Screens.Menu private void updateColour() { - Color4 defaultColour = Color4.White; - if (user.Value?.IsSupporter ?? false) - Colour = skin.Value.GetConfig(GlobalSkinColours.MenuGlow)?.Value ?? defaultColour; + Colour = skin.Value.GetConfig(GlobalSkinColours.MenuGlow)?.Value ?? Color4.White; else - Colour = defaultColour; + Colour = Color4.White; } } } From 3ed78688012d711fd374f5350b742625cda6f192 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Nov 2020 13:49:14 +0900 Subject: [PATCH 153/394] Scroll editor setup screen to file selector on display Previously the file selector would potentially display off-screen, making for confusing UX. Closes #10942. --- osu.Game/Graphics/Containers/SectionsContainer.cs | 2 ++ .../Edit/Setup/FileChooserLabelledTextBox.cs | 13 ++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index f32f8e0c67..81968de304 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using JetBrains.Annotations; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -14,6 +15,7 @@ namespace osu.Game.Graphics.Containers /// /// A container that can scroll to each section inside it. /// + [Cached] public class SectionsContainer : Container where T : Drawable { diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs index b802b3405a..5de6842b50 100644 --- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs @@ -3,10 +3,12 @@ using System; using System.IO; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; @@ -21,6 +23,9 @@ namespace osu.Game.Screens.Edit.Setup private readonly IBindable currentFile = new Bindable(); + [Resolved] + private SectionsContainer sectionsContainer { get; set; } + public FileChooserLabelledTextBox() { currentFile.BindValueChanged(onFileSelected); @@ -47,14 +52,16 @@ namespace osu.Game.Screens.Edit.Setup public void DisplayFileChooser() { - Target.Child = new FileSelector(validFileExtensions: ResourcesSection.AudioExtensions) + FileSelector fileSelector; + + Target.Child = fileSelector = new FileSelector(validFileExtensions: ResourcesSection.AudioExtensions) { RelativeSizeAxes = Axes.X, Height = 400, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, CurrentFile = { BindTarget = currentFile } }; + + sectionsContainer?.ScrollTo(fileSelector); } internal class FileChooserOsuTextBox : OsuTextBox From 898e2dae27f3f9badc0407ff745375803cec7345 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Nov 2020 14:27:02 +0900 Subject: [PATCH 154/394] Restore kiai time flashing behaviour --- osu.Game/Screens/Menu/LogoVisualisation.cs | 2 +- osu.Game/Screens/Menu/OsuLogo.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index c96b08902c..01b2a98c6e 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -174,7 +174,7 @@ namespace osu.Game.Screens.Menu // Assuming the logo is a circle, we don't need a second dimension. private float size; - private static readonly Color4 transparent_white = Color4.White.Opacity(0.1f); + private static readonly Color4 transparent_white = Color4.White.Opacity(0.2f); private float[] audioData; diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 8fa9d6e933..68d23e1a32 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -81,6 +81,8 @@ namespace osu.Game.Screens.Menu set => rippleContainer.FadeTo(value ? 1 : 0, transition_length, Easing.OutQuint); } + private const float visualizer_default_alpha = 0.5f; + private readonly Box flashLayer; private readonly Container impactContainer; @@ -144,6 +146,7 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, + Alpha = visualizer_default_alpha, Size = new Vector2(0.96f) }, new Container @@ -281,8 +284,7 @@ namespace osu.Game.Screens.Menu this.Delay(early_activation).Schedule(() => sampleBeat.Play()); logoBeatContainer - .ScaleTo(1 - 0.02f * amplitudeAdjust, early_activation, Easing.Out) - .Then() + .ScaleTo(1 - 0.02f * amplitudeAdjust, early_activation, Easing.Out).Then() .ScaleTo(1, beatLength * 2, Easing.OutQuint); ripple.ClearTransforms(); @@ -295,15 +297,13 @@ namespace osu.Game.Screens.Menu { flashLayer.ClearTransforms(); flashLayer - .FadeTo(0.2f * amplitudeAdjust, early_activation, Easing.Out) - .Then() + .FadeTo(0.2f * amplitudeAdjust, early_activation, Easing.Out).Then() .FadeOut(beatLength); visualizer.ClearTransforms(); visualizer - .FadeTo(0.9f * amplitudeAdjust, early_activation, Easing.Out) - .Then() - .FadeTo(0.5f, beatLength); + .FadeTo(visualizer_default_alpha * 1.8f * amplitudeAdjust, early_activation, Easing.Out).Then() + .FadeTo(visualizer_default_alpha, beatLength); } } From 1b33d3003924daed2d2995a4889725728fce4d46 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 23 Nov 2020 08:52:29 +0300 Subject: [PATCH 155/394] Simplify horizontal ticks creation --- .../Sections/Historical/ProfileLineChart.cs | 37 +++++-------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 1ce14fa245..7dbcb9ba16 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -115,9 +115,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical var min = values.Select(v => v.Count).Min(); var max = values.Select(v => v.Count).Max(); - var tick = getTick(getRange(max - min), 6); + var tick = getTick(max - min, 6); - bool tickIsDecimal = tick < 1.0; + // Prevent infinite loop in case if tick is zero + if (tick == 0) + tick = 1; double rollingRow = 0; @@ -126,7 +128,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical if (rollingRow >= min) { var y = -Interpolation.ValueAt(rollingRow, 0, 1f, min, max); - addRowTick(y, rollingRow, tickIsDecimal); + addRowTick(y, rollingRow); } rollingRow += tick; @@ -156,7 +158,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical } } - private void addRowTick(float y, double value, bool tickIsDecimal) + private void addRowTick(float y, double value) { rowTicksContainer.Add(new TickText { @@ -164,7 +166,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Origin = Anchor.CentreRight, RelativePositionAxes = Axes.Y, Margin = new MarginPadding { Right = 3 }, - Text = tickIsDecimal ? value.ToString("F1") : value.ToString("N0"), + Text = value.ToString("N0"), Font = OsuFont.GetFont(size: 12), Y = y }); @@ -204,28 +206,9 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }); } - private double getRange(double initialRange) + private long getTick(long range, int maxTicksCount) { - var exponent = Math.Floor(Math.Log10(initialRange)); - var fraction = initialRange / Math.Pow(10, exponent); - - double niceFraction; - - if (fraction <= 1.0) - niceFraction = 1.0; - else if (fraction <= 2.0) - niceFraction = 2.0; - else if (fraction <= 5.0) - niceFraction = 5.0; - else - niceFraction = 10.0; - - return niceFraction * Math.Pow(10, exponent); - } - - private double getTick(double range, int maxTicksCount) - { - var value = range / (maxTicksCount - 1); + var value = (float)range / (maxTicksCount - 1); var exponent = Math.Floor(Math.Log10(value)); var fraction = value / Math.Pow(10, exponent); @@ -241,7 +224,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical else niceFraction = 10.0; - return niceFraction * Math.Pow(10, exponent); + return (long)(niceFraction * Math.Pow(10, exponent)); } private class TickText : OsuSpriteText From 19faa2b9bbbcc2835e251a69ffbc2c6b8df6249d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Nov 2020 15:18:54 +0900 Subject: [PATCH 156/394] Add comment covering intentional call to ClearTransformsAfter --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 2 ++ osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index cd6c001172..889e748a4a 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -149,8 +149,10 @@ namespace osu.Game.Rulesets.Judgements private void runAnimation() { + // undo any transforms applies in ApplyMissAnimations/ApplyHitAnimations to get a sane initial state. ApplyTransformsAt(double.MinValue, true); ClearTransforms(true); + LifetimeStart = Result.TimeAbsolute; using (BeginAbsoluteSequence(Result.TimeAbsolute, true)) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ca49ed9e75..0fa76a733f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -439,6 +439,8 @@ namespace osu.Game.Rulesets.Objects.Drawables private void clearExistingStateTransforms() { base.ApplyTransformsAt(double.MinValue, true); + + // has to call this method directly (not ClearTransforms) to bypass the local ClearTransformsAfter override. base.ClearTransformsAfter(double.MinValue, true); } From 3c0ee7de9b12f17924e1b94e3bdc9d58f9e6850d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 23 Nov 2020 09:51:50 +0300 Subject: [PATCH 157/394] Add proper tests --- .../Online/TestSceneChartProfileSubsection.cs | 145 ++++++++++++++++++ .../Online/TestSceneProfileLineChart.cs | 55 ------- 2 files changed, 145 insertions(+), 55 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneChartProfileSubsection.cs delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneChartProfileSubsection.cs b/osu.Game.Tests/Visual/Online/TestSceneChartProfileSubsection.cs new file mode 100644 index 0000000000..4983dfba12 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneChartProfileSubsection.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 osu.Game.Overlays.Profile.Sections.Historical; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Users; +using NUnit.Framework; +using osu.Game.Overlays; +using osu.Framework.Allocation; +using System; +using System.Linq; +using osu.Framework.Testing; +using osu.Framework.Graphics.Shapes; +using static osu.Game.Users.User; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneChartProfileSubsection : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Red); + + private readonly Bindable user = new Bindable(); + private readonly PlayHistorySubsection section; + + public TestSceneChartProfileSubsection() + { + AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4 + }, + section = new PlayHistorySubsection(user) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + }); + } + + [Test] + public void TestNullValues() + { + AddStep("Load user", () => user.Value = user_with_null_values); + AddAssert("Section is hidden", () => section.Alpha == 0); + } + + [Test] + public void TestEmptyValues() + { + AddStep("Load user", () => user.Value = user_with_empty_values); + AddAssert("Section is hidden", () => section.Alpha == 0); + } + + [Test] + public void TestOveValue() + { + AddStep("Load user", () => user.Value = user_with_one_value); + AddAssert("Section is hidden", () => section.Alpha == 0); + } + + [Test] + public void TestTwoValues() + { + AddStep("Load user", () => user.Value = user_with_two_values); + AddAssert("Section is visible", () => section.Alpha == 1); + } + + [Test] + public void TestFilledValues() + { + AddStep("Load user", () => user.Value = user_with_filled_values); + AddAssert("Section is visible", () => section.Alpha == 1); + AddAssert("Array length is the same", () => user_with_filled_values.MonthlyPlaycounts.Length == getChartValuesLength()); + } + + [Test] + public void TestMissingValues() + { + AddStep("Load user", () => user.Value = user_with_missing_values); + AddAssert("Section is visible", () => section.Alpha == 1); + AddAssert("Array length is 7", () => getChartValuesLength() == 7); + } + + private int getChartValuesLength() => this.ChildrenOfType().Single().Values.Length; + + private static readonly User user_with_null_values = new User + { + Id = 1 + }; + + private static readonly User user_with_empty_values = new User + { + Id = 2, + MonthlyPlaycounts = Array.Empty() + }; + + private static readonly User user_with_one_value = new User + { + Id = 3, + MonthlyPlaycounts = new[] + { + new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 100 } + } + }; + + private static readonly User user_with_two_values = new User + { + Id = 4, + MonthlyPlaycounts = new[] + { + new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1 }, + new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 2 } + } + }; + + private static readonly User user_with_filled_values = new User + { + Id = 5, + MonthlyPlaycounts = new[] + { + new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1000 }, + new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 20 }, + new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 20000 }, + new UserHistoryCount { Date = new DateTime(2010, 8, 1), Count = 30 }, + new UserHistoryCount { Date = new DateTime(2010, 9, 1), Count = 50 }, + new UserHistoryCount { Date = new DateTime(2010, 10, 1), Count = 2000 }, + new UserHistoryCount { Date = new DateTime(2010, 11, 1), Count = 2100 } + } + }; + + private static readonly User user_with_missing_values = new User + { + Id = 6, + MonthlyPlaycounts = new[] + { + new UserHistoryCount { Date = new DateTime(2020, 1, 1), Count = 100 }, + new UserHistoryCount { Date = new DateTime(2020, 7, 1), Count = 200 } + } + }; + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs deleted file mode 100644 index 3d342b0d76..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneProfileLineChart.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Overlays.Profile.Sections.Historical; -using osu.Framework.Graphics; -using System; -using osu.Game.Overlays; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Containers; -using static osu.Game.Users.User; - -namespace osu.Game.Tests.Visual.Online -{ - public class TestSceneProfileLineChart : OsuTestScene - { - [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); - - public TestSceneProfileLineChart() - { - var values = new[] - { - new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1000 }, - new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 20 }, - new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 20000 }, - new UserHistoryCount { Date = new DateTime(2010, 8, 1), Count = 30 }, - new UserHistoryCount { Date = new DateTime(2010, 9, 1), Count = 50 }, - new UserHistoryCount { Date = new DateTime(2010, 10, 1), Count = 2000 }, - new UserHistoryCount { Date = new DateTime(2010, 11, 1), Count = 2100 } - }; - - AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4 - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Padding = new MarginPadding { Horizontal = 50 }, - Child = new ProfileLineChart - { - Values = values - } - } - }); - } - } -} From 087ea9c9a5366c3848a56ea479259065913929d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Nov 2020 20:51:38 +0100 Subject: [PATCH 158/394] Fix typo in test name --- osu.Game.Tests/Visual/Online/TestSceneChartProfileSubsection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChartProfileSubsection.cs b/osu.Game.Tests/Visual/Online/TestSceneChartProfileSubsection.cs index 4983dfba12..0c8ee5e0c5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChartProfileSubsection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChartProfileSubsection.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestOveValue() + public void TestOneValue() { AddStep("Load user", () => user.Value = user_with_one_value); AddAssert("Section is hidden", () => section.Alpha == 0); From 20f1775ddb11ff2e88c22475788b5784892f0621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Nov 2020 20:59:04 +0100 Subject: [PATCH 159/394] Rename test scene to match tested class --- ...ProfileSubsection.cs => TestScenePlayHistorySubsection.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Tests/Visual/Online/{TestSceneChartProfileSubsection.cs => TestScenePlayHistorySubsection.cs} (97%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChartProfileSubsection.cs b/osu.Game.Tests/Visual/Online/TestScenePlayHistorySubsection.cs similarity index 97% rename from osu.Game.Tests/Visual/Online/TestSceneChartProfileSubsection.cs rename to osu.Game.Tests/Visual/Online/TestScenePlayHistorySubsection.cs index 0c8ee5e0c5..c9dbc9dc24 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChartProfileSubsection.cs +++ b/osu.Game.Tests/Visual/Online/TestScenePlayHistorySubsection.cs @@ -16,7 +16,7 @@ using static osu.Game.Users.User; namespace osu.Game.Tests.Visual.Online { - public class TestSceneChartProfileSubsection : OsuTestScene + public class TestScenePlayHistorySubsection : OsuTestScene { [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Red); @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online private readonly Bindable user = new Bindable(); private readonly PlayHistorySubsection section; - public TestSceneChartProfileSubsection() + public TestScenePlayHistorySubsection() { AddRange(new Drawable[] { From e9ffeb8b5d1471e59e91cce116e316e54ffe3db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Nov 2020 21:09:42 +0100 Subject: [PATCH 160/394] Make missing date check more robust --- .../Profile/Sections/Historical/ChartProfileSubsection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs index 783ecec190..3fd334005f 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs @@ -76,7 +76,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical static bool hasMissingDates(UserHistoryCount prev, UserHistoryCount current) { var possibleCurrent = prev.Date.AddMonths(1); - return possibleCurrent != current.Date; + return possibleCurrent < current.Date; } } From bb5aa9a9c914d2fa0bd57431dfa7d737b00831ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Nov 2020 21:24:37 +0100 Subject: [PATCH 161/394] Guard against empty values early --- .../Profile/Sections/Historical/ProfileLineChart.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 7dbcb9ba16..af2ef5a75c 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -26,8 +26,10 @@ namespace osu.Game.Overlays.Profile.Sections.Historical get => values; set { - values = value; - graph.Values = values; + if (value.Length == 0) + throw new ArgumentException("At least one value expected!", nameof(value)); + + graph.Values = values = value; createRowTicks(); createColumnTicks(); From 7b0d3dfe0cee8696f7251465ba63a7755d161ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Nov 2020 21:38:04 +0100 Subject: [PATCH 162/394] Refactor tick calculation code for readability --- .../Sections/Historical/ProfileLineChart.cs | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index af2ef5a75c..ecc85fb48d 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical var min = values.Select(v => v.Count).Min(); var max = values.Select(v => v.Count).Max(); - var tick = getTick(max - min, 6); + var tick = getTickInterval(max - min, 6); // Prevent infinite loop in case if tick is zero if (tick == 0) @@ -208,25 +208,32 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }); } - private long getTick(long range, int maxTicksCount) + private long getTickInterval(long range, int maxTicksCount) { - var value = (float)range / (maxTicksCount - 1); + // this interval is what would be achieved if the interval was divided perfectly evenly into maxTicksCount ticks. + // can contain ugly fractional parts. + var exactTickInterval = (float)range / (maxTicksCount - 1); - var exponent = Math.Floor(Math.Log10(value)); - var fraction = value / Math.Pow(10, exponent); + // the ideal ticks start with a 1, 2 or 5, and are multipliers of powers of 10. + // first off, use log10 to calculate the number of digits in the "exact" interval. + var numberOfDigits = Math.Floor(Math.Log10(exactTickInterval)); + var tickBase = Math.Pow(10, numberOfDigits); + // then see how the exact tick relates to the power of 10. + var exactTickMultiplier = exactTickInterval / tickBase; - double niceFraction; + double tickMultiplier; - if (fraction < 1.5) - niceFraction = 1.0; - else if (fraction < 3) - niceFraction = 2.0; - else if (fraction < 7) - niceFraction = 5.0; + // round up the fraction to start with a 1, 2 or 5. closest match wins. + if (exactTickMultiplier < 1.5) + tickMultiplier = 1.0; + else if (exactTickMultiplier < 3) + tickMultiplier = 2.0; + else if (exactTickMultiplier < 7) + tickMultiplier = 5.0; else - niceFraction = 10.0; + tickMultiplier = 10.0; - return (long)(niceFraction * Math.Pow(10, exponent)); + return Math.Max((long)(tickMultiplier * tickBase), 1); } private class TickText : OsuSpriteText From 8347ecf494f6824d1fe0ee36b6094e434cc31196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Nov 2020 21:52:47 +0100 Subject: [PATCH 163/394] Simplify row tick creation code --- .../Sections/Historical/ProfileLineChart.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index ecc85fb48d..c1eb0811fb 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -117,23 +117,15 @@ namespace osu.Game.Overlays.Profile.Sections.Historical var min = values.Select(v => v.Count).Min(); var max = values.Select(v => v.Count).Max(); - var tick = getTickInterval(max - min, 6); + var tickInterval = getTickInterval(max - min, 6); - // Prevent infinite loop in case if tick is zero - if (tick == 0) - tick = 1; - - double rollingRow = 0; - - while (rollingRow <= max) + for (long currentTick = 0; currentTick <= max; currentTick += tickInterval) { - if (rollingRow >= min) - { - var y = -Interpolation.ValueAt(rollingRow, 0, 1f, min, max); - addRowTick(y, rollingRow); - } + if (currentTick < min) + continue; - rollingRow += tick; + float y = -Interpolation.ValueAt(currentTick, 0, 1f, min, max); + addRowTick(y, currentTick); } } From 5701b32bae6c6d7dd5e4f7d5cf450415d2d5cc40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Nov 2020 22:12:32 +0100 Subject: [PATCH 164/394] Handle constant graphs better --- .../Online/TestScenePlayHistorySubsection.cs | 40 ++++++++++++++++++- osu.Game/Graphics/UserInterface/LineGraph.cs | 6 ++- .../Sections/Historical/ProfileLineChart.cs | 12 +++++- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestScenePlayHistorySubsection.cs b/osu.Game.Tests/Visual/Online/TestScenePlayHistorySubsection.cs index c9dbc9dc24..cf5ecf5bf2 100644 --- a/osu.Game.Tests/Visual/Online/TestScenePlayHistorySubsection.cs +++ b/osu.Game.Tests/Visual/Online/TestScenePlayHistorySubsection.cs @@ -69,6 +69,20 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Section is visible", () => section.Alpha == 1); } + [Test] + public void TestConstantValues() + { + AddStep("Load user", () => user.Value = user_with_constant_values); + AddAssert("Section is visible", () => section.Alpha == 1); + } + + [Test] + public void TestConstantZeroValues() + { + AddStep("Load user", () => user.Value = user_with_zero_values); + AddAssert("Section is visible", () => section.Alpha == 1); + } + [Test] public void TestFilledValues() { @@ -117,10 +131,32 @@ namespace osu.Game.Tests.Visual.Online } }; - private static readonly User user_with_filled_values = new User + private static readonly User user_with_constant_values = new User { Id = 5, MonthlyPlaycounts = new[] + { + new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 5 }, + new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 5 }, + new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 5 } + } + }; + + private static readonly User user_with_zero_values = new User + { + Id = 6, + MonthlyPlaycounts = new[] + { + new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 0 }, + new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 0 }, + new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 0 } + } + }; + + private static readonly User user_with_filled_values = new User + { + Id = 7, + MonthlyPlaycounts = new[] { new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1000 }, new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 20 }, @@ -134,7 +170,7 @@ namespace osu.Game.Tests.Visual.Online private static readonly User user_with_missing_values = new User { - Id = 6, + Id = 8, MonthlyPlaycounts = new[] { new UserHistoryCount { Date = new DateTime(2020, 1, 1), Count = 100 }, diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index 42b523fc5c..70db26c817 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -119,7 +119,11 @@ namespace osu.Game.Graphics.UserInterface protected float GetYPosition(float value) { - if (ActualMaxValue == ActualMinValue) return 0; + if (ActualMaxValue == ActualMinValue) + // show line at top if the only value on the graph is positive, + // and at bottom if the only value on the graph is zero or negative. + // just kind of makes most sense intuitively. + return value > 1 ? 0 : 1; return (ActualMaxValue - value) / (ActualMaxValue - ActualMinValue); } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index c1eb0811fb..989871745d 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -124,8 +124,16 @@ namespace osu.Game.Overlays.Profile.Sections.Historical if (currentTick < min) continue; - float y = -Interpolation.ValueAt(currentTick, 0, 1f, min, max); - addRowTick(y, currentTick); + float y; + // special-case the min == max case to match LineGraph. + // lerp isn't really well-defined over a zero interval anyway. + if (min == max) + y = currentTick > 1 ? 1 : 0; + else + y = Interpolation.ValueAt(currentTick, 0, 1f, min, max); + + // y axis is inverted in graph-like coordinates. + addRowTick(-y, currentTick); } } From 52f5473cc043eb4ed12f3e9e395c51eff9a1a57a Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 23 Nov 2020 15:13:58 -0800 Subject: [PATCH 165/394] Set global action as a parameter in toast --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Input/KeyBindingStore.cs | 2 +- .../Overlays/Music/MusicKeyBindingHandler.cs | 16 ++++----- osu.Game/Overlays/OSD/Toast.cs | 33 +++++++++++++++++-- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 89a6ee8b07..44ac48d83a 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -196,7 +196,7 @@ namespace osu.Game.Configuration public Func LookupSkinName { private get; set; } - public Func LookupKeyBindings { get; set; } + public Func LookupKeyBindings { get; set; } } public enum OsuSetting diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index bc73d74d74..f4b7c873d5 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -37,7 +37,7 @@ namespace osu.Game.Input /// /// The action to lookup. /// A set of display strings for all the user's key configuration for the action. - public IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction) + public IEnumerable GetReadableKeyCombinationsFor(GlobalAction? globalAction) { foreach (var action in Query().Where(b => (GlobalAction)b.Action == globalAction)) { diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index 0d6158d46f..fa8180b7c0 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Overlays.OSD; @@ -26,9 +25,6 @@ namespace osu.Game.Overlays.Music [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } - [Resolved] - private OsuConfigManager config { get; set; } - public bool OnPressed(GlobalAction action) { if (beatmap.Disabled) @@ -41,11 +37,11 @@ namespace osu.Game.Overlays.Music bool wasPlaying = musicController.IsPlaying; if (musicController.TogglePause()) - onScreenDisplay?.Display(new MusicActionToast(wasPlaying ? "Pause track" : "Play track", config.LookupKeyBindings(action))); + onScreenDisplay?.Display(new MusicActionToast(wasPlaying ? "Pause track" : "Play track", action)); return true; case GlobalAction.MusicNext: - musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast("Next track", config.LookupKeyBindings(action)))); + musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast("Next track", action))); return true; @@ -55,11 +51,11 @@ namespace osu.Game.Overlays.Music switch (res) { case PreviousTrackResult.Restart: - onScreenDisplay?.Display(new MusicActionToast("Restart track", config.LookupKeyBindings(action))); + onScreenDisplay?.Display(new MusicActionToast("Restart track", action)); break; case PreviousTrackResult.Previous: - onScreenDisplay?.Display(new MusicActionToast("Previous track", config.LookupKeyBindings(action))); + onScreenDisplay?.Display(new MusicActionToast("Previous track", action)); break; } }); @@ -76,8 +72,8 @@ namespace osu.Game.Overlays.Music private class MusicActionToast : Toast { - public MusicActionToast(string action, string shortcut) - : base("Music Playback", action, shortcut) + public MusicActionToast(string value, GlobalAction action) + : base("Music Playback", value, action: action) { } } diff --git a/osu.Game/Overlays/OSD/Toast.cs b/osu.Game/Overlays/OSD/Toast.cs index 1497ca8fa8..d32d63055f 100644 --- a/osu.Game/Overlays/OSD/Toast.cs +++ b/osu.Game/Overlays/OSD/Toast.cs @@ -1,11 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; 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.Sprites; +using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; @@ -16,12 +19,22 @@ namespace osu.Game.Overlays.OSD private const int toast_minimum_width = 240; private readonly Container content; + + private readonly OsuSpriteText spriteText; + + private readonly string shortcut; + + private readonly GlobalAction? action; + protected override Container Content => content; protected readonly OsuSpriteText ValueText; - protected Toast(string description, string value, string shortcut) + protected Toast(string description, string value, string shortcut = null, GlobalAction? action = null) { + this.shortcut = shortcut; + this.action = action; + Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -68,7 +81,7 @@ namespace osu.Game.Overlays.OSD Origin = Anchor.Centre, Text = value }, - new OsuSpriteText + spriteText = new OsuSpriteText { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, @@ -76,9 +89,23 @@ namespace osu.Game.Overlays.OSD Alpha = 0.3f, Margin = new MarginPadding { Bottom = 15 }, Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = string.IsNullOrEmpty(shortcut) ? "NO KEY BOUND" : shortcut.ToUpperInvariant() }, }; } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + string text; + + if (action != null) + text = config.LookupKeyBindings(action); + else if (!string.IsNullOrEmpty(shortcut)) + text = shortcut; + else + text = "no key bound"; + + spriteText.Text = text.ToUpperInvariant(); + } } } From 44ca67c534ab40635f632e8b4fb6b2b1e124f4bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Nov 2020 13:10:11 +0900 Subject: [PATCH 166/394] Simplify fill logic and add xmldoc --- .../Historical/ChartProfileSubsection.cs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs index 3fd334005f..885b12ca6d 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -52,32 +53,30 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Hide(); } - private UserHistoryCount[] fillZeroValues(UserHistoryCount[] values) + /// + /// Add entries for any missing months (filled with zero values). + /// + private UserHistoryCount[] fillZeroValues(UserHistoryCount[] historyEntries) { - var newValues = new List { values[0] }; - var newLast = values[0]; + var filledHistoryEntries = new List(); - for (int i = 1; i < values.Length; i++) + foreach (var entry in historyEntries) { - while (hasMissingDates(newLast, values[i])) + var lastFilled = filledHistoryEntries.LastOrDefault(); + + while (lastFilled?.Date.AddMonths(1) < entry.Date) { - newValues.Add(newLast = new UserHistoryCount + filledHistoryEntries.Add(lastFilled = new UserHistoryCount { Count = 0, - Date = newLast.Date.AddMonths(1) + Date = lastFilled.Date.AddMonths(1) }); } - newValues.Add(newLast = values[i]); + filledHistoryEntries.Add(entry); } - return newValues.ToArray(); - - static bool hasMissingDates(UserHistoryCount prev, UserHistoryCount current) - { - var possibleCurrent = prev.Date.AddMonths(1); - return possibleCurrent < current.Date; - } + return filledHistoryEntries.ToArray(); } protected abstract UserHistoryCount[] GetValues(User user); From 82640418ba1e140d7d4d4d109a15a29e155fe24f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Nov 2020 13:12:04 +0900 Subject: [PATCH 167/394] Invert hide logic for readability --- .../Profile/Sections/Historical/ChartProfileSubsection.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs index 885b12ca6d..b82773155d 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs @@ -43,14 +43,14 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { var values = GetValues(e.NewValue); - if (values?.Length > 1) + if (values == null || values.Length <= 1) { - chart.Values = fillZeroValues(values); - Show(); + Hide(); return; } - Hide(); + chart.Values = fillZeroValues(values); + Show(); } /// From e36b1051c18ba0fc4d2210cea84b269c4547e2b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Nov 2020 13:15:59 +0900 Subject: [PATCH 168/394] Add spacing between inline comments --- .../Overlays/Profile/Sections/Historical/ProfileLineChart.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 989871745d..f02aa36b6c 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -125,6 +125,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical continue; float y; + // special-case the min == max case to match LineGraph. // lerp isn't really well-defined over a zero interval anyway. if (min == max) @@ -218,6 +219,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical // first off, use log10 to calculate the number of digits in the "exact" interval. var numberOfDigits = Math.Floor(Math.Log10(exactTickInterval)); var tickBase = Math.Pow(10, numberOfDigits); + // then see how the exact tick relates to the power of 10. var exactTickMultiplier = exactTickInterval / tickBase; From 1fd4b04767bffde0750165ac1064ef13376bfacb Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 23 Nov 2020 20:43:46 -0800 Subject: [PATCH 169/394] Just set music shortcut text locally --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Input/KeyBindingStore.cs | 2 +- .../Overlays/Music/MusicKeyBindingHandler.cs | 12 ++++++- osu.Game/Overlays/OSD/Toast.cs | 34 +++---------------- 4 files changed, 18 insertions(+), 32 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 44ac48d83a..89a6ee8b07 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -196,7 +196,7 @@ namespace osu.Game.Configuration public Func LookupSkinName { private get; set; } - public Func LookupKeyBindings { get; set; } + public Func LookupKeyBindings { get; set; } } public enum OsuSetting diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index f4b7c873d5..bc73d74d74 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -37,7 +37,7 @@ namespace osu.Game.Input /// /// The action to lookup. /// A set of display strings for all the user's key configuration for the action. - public IEnumerable GetReadableKeyCombinationsFor(GlobalAction? globalAction) + public IEnumerable GetReadableKeyCombinationsFor(GlobalAction globalAction) { foreach (var action in Query().Where(b => (GlobalAction)b.Action == globalAction)) { diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index fa8180b7c0..f06e02e5e1 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Overlays.OSD; @@ -72,9 +73,18 @@ namespace osu.Game.Overlays.Music private class MusicActionToast : Toast { + private readonly GlobalAction action; + public MusicActionToast(string value, GlobalAction action) - : base("Music Playback", value, action: action) + : base("Music Playback", value, string.Empty) { + this.action = action; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + ShortcutText.Text = config.LookupKeyBindings(action).ToUpperInvariant(); } } } diff --git a/osu.Game/Overlays/OSD/Toast.cs b/osu.Game/Overlays/OSD/Toast.cs index d32d63055f..4a6316df3f 100644 --- a/osu.Game/Overlays/OSD/Toast.cs +++ b/osu.Game/Overlays/OSD/Toast.cs @@ -1,14 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; @@ -20,21 +17,14 @@ namespace osu.Game.Overlays.OSD private readonly Container content; - private readonly OsuSpriteText spriteText; - - private readonly string shortcut; - - private readonly GlobalAction? action; - protected override Container Content => content; protected readonly OsuSpriteText ValueText; - protected Toast(string description, string value, string shortcut = null, GlobalAction? action = null) - { - this.shortcut = shortcut; - this.action = action; + protected readonly OsuSpriteText ShortcutText; + protected Toast(string description, string value, string shortcut) + { Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -81,7 +71,7 @@ namespace osu.Game.Overlays.OSD Origin = Anchor.Centre, Text = value }, - spriteText = new OsuSpriteText + ShortcutText = new OsuSpriteText { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, @@ -89,23 +79,9 @@ namespace osu.Game.Overlays.OSD Alpha = 0.3f, Margin = new MarginPadding { Bottom = 15 }, Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = string.IsNullOrEmpty(shortcut) ? "NO KEY BOUND" : shortcut.ToUpperInvariant() }, }; } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - string text; - - if (action != null) - text = config.LookupKeyBindings(action); - else if (!string.IsNullOrEmpty(shortcut)) - text = shortcut; - else - text = "no key bound"; - - spriteText.Text = text.ToUpperInvariant(); - } } } From ee33f62809067247049f03d6900eb7e7077bf55f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Nov 2020 15:19:51 +0900 Subject: [PATCH 170/394] Fix DrawableJudgement not always animating correctly on skin change If the skin is changed before gameplay has started (at the loading screen) it is possible for a sequence of events to occur which results in the animation not being played: - `SkinReloadableDrawable` runs its BDL load (and calls `OnSkinChanged` once) - User changes skin, triggering `DrawableJudgement`'s skin change handling (binding directly on the `SkinSource` locally) - This will call `PrepareDrawables` and reinitialise the `SkinnableDrawable` child hierarchy, then immediately apply the animations to it. - The new `SkinnableDrawable` will then get the `SkinChanged` event and schedule a handler for it, which will run on its first Update call. - Any added animations will be lost as a result. Fixed by binding directly to the `SkinnableDrawable`'s `OnSkinChanged`. This has the added bonus of not needing to reinitialise the child hierarchy on skin change (which felt a bit weird in the first place). --- .../Rulesets/Judgements/DrawableJudgement.cs | 63 ++++++++----------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 889e748a4a..45129c17ea 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -32,9 +32,6 @@ namespace osu.Game.Rulesets.Judgements private readonly Container aboveHitObjectsContent; - [Resolved] - private ISkinSource skinSource { get; set; } - /// /// Duration of initial fade in. /// @@ -78,29 +75,6 @@ namespace osu.Game.Rulesets.Judgements public Drawable GetProxyAboveHitObjectsContent() => aboveHitObjectsContent.CreateProxy(); - protected override void LoadComplete() - { - base.LoadComplete(); - skinSource.SourceChanged += onSkinChanged; - } - - private void onSkinChanged() - { - // on a skin change, the child component will update but not get correctly triggered to play its animation. - // we need to trigger a reinitialisation to make things right. - currentDrawableType = null; - - PrepareForUse(); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (skinSource != null) - skinSource.SourceChanged -= onSkinChanged; - } - /// /// Apply top-level animations to the current judgement when successfully hit. /// If displaying components which require lifetime extensions, manually adjusting is required. @@ -142,16 +116,17 @@ namespace osu.Game.Rulesets.Judgements Debug.Assert(Result != null); - prepareDrawables(); - runAnimation(); } private void runAnimation() { + // is a no-op if the drawables are already in a correct state. + prepareDrawables(); + // undo any transforms applies in ApplyMissAnimations/ApplyHitAnimations to get a sane initial state. - ApplyTransformsAt(double.MinValue, true); - ClearTransforms(true); + // ApplyTransformsAt(double.MinValue, true); + // ClearTransforms(true); LifetimeStart = Result.TimeAbsolute; @@ -193,6 +168,8 @@ namespace osu.Game.Rulesets.Judgements private void prepareDrawables() { + Logger.Log("prepareDrawables on DrawableJudgement"); + var type = Result?.Type ?? HitResult.Perfect; //TODO: better default type from ruleset // todo: this should be removed once judgements are always pooled. @@ -203,7 +180,6 @@ namespace osu.Game.Rulesets.Judgements if (JudgementBody != null) RemoveInternal(JudgementBody); - aboveHitObjectsContent.Clear(); AddInternal(JudgementBody = new SkinnableDrawable(new GameplaySkinComponent(type), _ => CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling) { @@ -211,14 +187,29 @@ namespace osu.Game.Rulesets.Judgements Origin = Anchor.Centre, }); - if (JudgementBody.Drawable is IAnimatableJudgement animatable) + JudgementBody.OnSkinChanged += () => { - var proxiedContent = animatable.GetAboveHitObjectsProxiedContent(); - if (proxiedContent != null) - aboveHitObjectsContent.Add(proxiedContent); - } + // on a skin change, the child component will update but not get correctly triggered to play its animation (or proxy the newly created content). + // we need to trigger a reinitialisation to make things right. + proxyContent(); + runAnimation(); + }; + + proxyContent(); currentDrawableType = type; + + void proxyContent() + { + aboveHitObjectsContent.Clear(); + + if (JudgementBody.Drawable is IAnimatableJudgement animatable) + { + var proxiedContent = animatable.GetAboveHitObjectsProxiedContent(); + if (proxiedContent != null) + aboveHitObjectsContent.Add(proxiedContent); + } + } } protected virtual Drawable CreateDefaultJudgement(HitResult result) => new DefaultJudgementPiece(result); From 168226067750b572272903939f59bd4cbf5061cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Nov 2020 15:28:03 +0900 Subject: [PATCH 171/394] Remove left over logging line --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 45129c17ea..4ed3b75f2e 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -168,8 +168,6 @@ namespace osu.Game.Rulesets.Judgements private void prepareDrawables() { - Logger.Log("prepareDrawables on DrawableJudgement"); - var type = Result?.Type ?? HitResult.Perfect; //TODO: better default type from ruleset // todo: this should be removed once judgements are always pooled. From 72b8eef36e514c430be84abb82d5eabe1288af20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Nov 2020 15:41:56 +0900 Subject: [PATCH 172/394] Add ability to pause/resume replay playback --- .../Input/Bindings/GlobalActionContainer.cs | 6 ++++- osu.Game/Screens/Play/Player.cs | 6 ++++- osu.Game/Screens/Play/ReplayPlayer.cs | 23 ++++++++++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index e5d3a89a88..77f57bd637 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -69,6 +69,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), + new KeyBinding(InputKey.Space, GlobalAction.PauseReplay), new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), }; @@ -175,7 +176,7 @@ namespace osu.Game.Input.Bindings [Description("Toggle notifications")] ToggleNotifications, - [Description("Pause")] + [Description("Pause Gameplay")] PauseGameplay, // Editor @@ -196,5 +197,8 @@ namespace osu.Game.Input.Bindings [Description("Random Skin")] RandomSkin, + + [Description("Pause Replay")] + PauseReplay, } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d0a83e3c22..7979b635aa 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -339,7 +339,11 @@ namespace osu.Game.Screens.Play AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded }, IsCounting = false }, - RequestSeek = GameplayClockContainer.Seek, + RequestSeek = time => + { + GameplayClockContainer.Seek(time); + GameplayClockContainer.Start(); + }, Anchor = Anchor.Centre, Origin = Anchor.Centre }, diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 3a4298f22d..2c0b766a17 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -1,12 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; using osu.Game.Scoring; using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Play { - public class ReplayPlayer : Player + public class ReplayPlayer : Player, IKeyBindingHandler { protected readonly Score Score; @@ -35,5 +37,24 @@ namespace osu.Game.Screens.Play return Score.ScoreInfo; } + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.PauseReplay: + if (GameplayClockContainer.IsPaused.Value) + GameplayClockContainer.Start(); + else + GameplayClockContainer.Stop(); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } } } From 1d82557d9f5158006d4c29af41d8ce583e6758f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Nov 2020 15:42:14 +0900 Subject: [PATCH 173/394] Avoid blocking global actions when skip overlay is not actually active --- osu.Game/Screens/Play/SkipOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index b123757ded..92b304de91 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -133,6 +133,9 @@ namespace osu.Game.Screens.Play switch (action) { case GlobalAction.SkipCutscene: + if (!button.Enabled.Value) + return false; + button.Click(); return true; } From bd1dad5477f46c041b176d6644714e5f7f126291 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Nov 2020 15:54:27 +0900 Subject: [PATCH 174/394] Remove null allowance for now --- osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs index 5de6842b50..6e2737256a 100644 --- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Edit.Setup CurrentFile = { BindTarget = currentFile } }; - sectionsContainer?.ScrollTo(fileSelector); + sectionsContainer.ScrollTo(fileSelector); } internal class FileChooserOsuTextBox : OsuTextBox From b9c1f782fa00b52beb0b29772ec9767d45b3e2db Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 17:03:26 +0900 Subject: [PATCH 175/394] Remove type parameter from DrawableCatchHitObject --- .../Objects/Drawables/DrawableBananaShower.cs | 2 +- .../Drawables/DrawableCatchHitObject.cs | 19 +++---------------- .../Objects/Drawables/DrawableDroplet.cs | 4 ++-- .../Objects/Drawables/DrawableFruit.cs | 4 ++-- .../Objects/Drawables/DrawableJuiceStream.cs | 2 +- 5 files changed, 9 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs index 4ce80aceb8..2215e1d983 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public class DrawableBananaShower : DrawableCatchHitObject + public class DrawableBananaShower : DrawableCatchHitObject { private readonly Func> createDrawableRepresentation; private readonly Container bananaContainer; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 7922510a49..07f1f79243 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -13,12 +13,11 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public abstract class PalpableDrawableCatchHitObject : DrawableCatchHitObject - where TObject : PalpableCatchHitObject + public abstract class PalpableDrawableCatchHitObject : DrawableCatchHitObject { protected Container ScaleContainer { get; private set; } - protected PalpableDrawableCatchHitObject(TObject hitObject) + protected PalpableDrawableCatchHitObject(CatchHitObject hitObject) : base(hitObject) { Origin = Anchor.Centre; @@ -46,19 +45,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables comboColours[(HitObject.IndexInBeatmap + 1) % comboColours.Count]; } - public abstract class DrawableCatchHitObject : DrawableCatchHitObject - where TObject : CatchHitObject - { - public new TObject HitObject; - - protected DrawableCatchHitObject(TObject hitObject) - : base(hitObject) - { - HitObject = hitObject; - Anchor = Anchor.BottomLeft; - } - } - public abstract class DrawableCatchHitObject : DrawableHitObject { protected override double InitialLifetimeOffset => HitObject.TimePreempt; @@ -73,6 +59,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables : base(hitObject) { X = hitObject.X; + Anchor = Anchor.BottomLeft; } public Func CheckPosition; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index 688240fd86..9db64eba6e 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -8,11 +8,11 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public class DrawableDroplet : PalpableDrawableCatchHitObject + public class DrawableDroplet : PalpableDrawableCatchHitObject { public override bool StaysOnPlate => false; - public DrawableDroplet(Droplet h) + public DrawableDroplet(CatchHitObject h) : base(h) { } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index c1c34e4157..f87c8866b1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -8,9 +8,9 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public class DrawableFruit : PalpableDrawableCatchHitObject + public class DrawableFruit : PalpableDrawableCatchHitObject { - public DrawableFruit(Fruit h) + public DrawableFruit(CatchHitObject h) : base(h) { } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs index 7bc016d94f..af4a269404 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs @@ -10,7 +10,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public class DrawableJuiceStream : DrawableCatchHitObject + public class DrawableJuiceStream : DrawableCatchHitObject { private readonly Func> createDrawableRepresentation; private readonly Container dropletContainer; From c9a41f9dae9ece4d7de354f1137b521d02d8add0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Nov 2020 17:14:39 +0900 Subject: [PATCH 176/394] Make all objects in selection candidates for spatial snapping Closes #10898. --- .../Editor/TestSceneManiaBeatSnapGrid.cs | 5 +++ .../Editor/TestSceneOsuDistanceSnapGrid.cs | 3 ++ .../Edit/OsuHitObjectComposer.cs | 11 +++++- .../Editing/TestSceneDistanceSnapGrid.cs | 3 ++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 ++ .../Rulesets/Edit/IPositionSnapProvider.cs | 12 ++++++- .../Compose/Components/BlueprintContainer.cs | 34 ++++++++++++++----- .../Compose/Components/Timeline/Timeline.cs | 3 ++ 8 files changed, 64 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs index 654b752001..538a51db5f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs @@ -96,6 +96,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor throw new System.NotImplementedException(); } + public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) + { + throw new System.NotImplementedException(); + } + public override float GetBeatSnapDistanceAt(double referenceTime) { throw new System.NotImplementedException(); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index 1232369a0b..9af2a99470 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -174,6 +174,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private class SnapProvider : IPositionSnapProvider { + public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => + new SnapResult(screenSpacePosition, null); + public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index bfa8ab4431..0490e8b8ce 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -105,11 +105,20 @@ namespace osu.Game.Rulesets.Osu.Edit } } - public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) { if (snapToVisibleBlueprints(screenSpacePosition, out var snapResult)) return snapResult; + return new SnapResult(screenSpacePosition, null); + } + + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + { + var positionSnap = SnapScreenSpacePositionToValidPosition(screenSpacePosition); + if (positionSnap.ScreenSpacePosition != screenSpacePosition) + return positionSnap; + // will be null if distance snap is disabled or not feasible for the current time value. if (distanceSnapGrid == null) return base.SnapScreenSpacePositionToValidTime(screenSpacePosition); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index 8190cf5f89..11830ebe35 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -153,6 +153,9 @@ namespace osu.Game.Tests.Visual.Editing private class SnapProvider : IPositionSnapProvider { + public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => + new SnapResult(screenSpacePosition, null); + public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); public float GetBeatSnapDistanceAt(double referenceTime) => 10; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b90aa6863a..35852f60ea 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -442,6 +442,9 @@ namespace osu.Game.Rulesets.Edit public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); + public virtual SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => + new SnapResult(screenSpacePosition, null); + public abstract float GetBeatSnapDistanceAt(double referenceTime); public abstract float DurationToDistance(double referenceTime, double duration); diff --git a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index cce631464f..4664f3808c 100644 --- a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs @@ -8,12 +8,22 @@ namespace osu.Game.Rulesets.Edit public interface IPositionSnapProvider { /// - /// Given a position, find a valid time snap. + /// Given a position, find a valid time and position snap. /// + /// + /// This call should be equivalent to running with any additional logic that can be performed without the time immutability restriction. + /// /// The screen-space position to be snapped. /// The time and position post-snapping. SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); + /// + /// Given a position, find a value position snap, restricting time to its input value. + /// + /// The screen-space position to be snapped. + /// The position post-snapping. Time will always be null. + SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition); + /// /// Retrieves the distance between two points within a timing point that are one beat length apart. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index df9cadebfc..e9f5238980 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -424,7 +424,7 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Selection Movement - private Vector2? movementBlueprintOriginalPosition; + private Vector2[] movementBlueprintOriginalPositions; private SelectionBlueprint movementBlueprint; private bool isDraggingBlueprint; @@ -442,8 +442,9 @@ namespace osu.Game.Screens.Edit.Compose.Components return; // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject - movementBlueprint = SelectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First(); - movementBlueprintOriginalPosition = movementBlueprint.ScreenSpaceSelectionPoint; // todo: unsure if correct + var orderedSelection = SelectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime); + movementBlueprint = orderedSelection.First(); + movementBlueprintOriginalPositions = orderedSelection.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); } /// @@ -459,12 +460,29 @@ namespace osu.Game.Screens.Edit.Compose.Components if (snapProvider == null) return true; - Debug.Assert(movementBlueprintOriginalPosition != null); + Debug.Assert(movementBlueprintOriginalPositions != null); - HitObject draggedObject = movementBlueprint.HitObject; + Vector2 distanceTravelled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; + + // check for positional snap for every object in selection (for things like object-object snapping) + for (var i = 0; i < movementBlueprintOriginalPositions.Length; i++) + { + var testPosition = movementBlueprintOriginalPositions[i] + distanceTravelled; + + var positionalResult = snapProvider.SnapScreenSpacePositionToValidPosition(testPosition); + + if (positionalResult.ScreenSpacePosition == testPosition) continue; + + // attempt to move the objects, and abort any time based snapping if we can. + if (SelectionHandler.HandleMovement(new MoveSelectionEvent(SelectionHandler.SelectedBlueprints.ElementAt(i), positionalResult.ScreenSpacePosition))) + return true; + } + + // if no positional snapping could be performed, try unrestricted snapping from the earliest + // hitobject in the selection. // The final movement position, relative to movementBlueprintOriginalPosition. - Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; + Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled; // Retrieve a snapped position. var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); @@ -476,7 +494,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (result.Time.HasValue) { // Apply the start time at the newly snapped-to position - double offset = result.Time.Value - draggedObject.StartTime; + double offset = result.Time.Value - movementBlueprint.HitObject.StartTime; foreach (HitObject obj in Beatmap.SelectedHitObjects) { @@ -497,7 +515,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (movementBlueprint == null) return false; - movementBlueprintOriginalPosition = null; + movementBlueprintOriginalPositions = null; movementBlueprint = null; return true; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index f6675902fc..20836c0e68 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -224,6 +224,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// public double VisibleRange => track.Length / Zoom; + public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => + new SnapResult(screenSpacePosition, null); + public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition)))); From 09f2a85d71bcca9753e6ed622574ac6ff29cd663 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Nov 2020 17:40:00 +0900 Subject: [PATCH 177/394] Fix potential test failure due to precision check missing --- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index 1ca94df26b..8212a16540 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor var first = (OsuHitObject)objects.First(); var second = (OsuHitObject)objects.Last(); - return first.Position == second.Position; + return Precision.AlmostEquals(first.EndPosition, second.Position); }); } From 4eef6c0d4072ef8a2670b0a40b4fcfd07fad1303 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Nov 2020 17:59:18 +0900 Subject: [PATCH 178/394] Add test coverage --- .../Editor/TestSceneObjectObjectSnap.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index 8212a16540..d20be90001 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -86,5 +86,55 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor return Precision.AlmostEquals(first.EndPosition, second.Position); }); } + + [Test] + public void TestSecondCircleInSelectionAlsoSnaps() + { + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre)); + + AddStep("disable distance snap", () => InputManager.Key(Key.Q)); + + AddStep("enter placement mode", () => InputManager.Key(Key.Number2)); + + AddStep("place first object", () => InputManager.Click(MouseButton.Left)); + + AddStep("move mouse right", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.2f, 0))); + AddStep("place second object", () => InputManager.Click(MouseButton.Left)); + + AddStep("move mouse down", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(0, playfield.ScreenSpaceDrawQuad.Width * 0.2f))); + AddStep("place third object", () => InputManager.Click(MouseButton.Left)); + + AddStep("enter selection mode", () => InputManager.Key(Key.Number1)); + + AddStep("select objects 2 and 3", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects.Skip(1))); + + AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); + + AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.02f, 0))); + + AddAssert("object 3 snapped to 1", () => + { + var objects = EditorBeatmap.HitObjects; + + var first = (OsuHitObject)objects.First(); + var third = (OsuHitObject)objects.Last(); + + return Precision.AlmostEquals(first.EndPosition, third.Position); + }); + + AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * -0.22f, playfield.ScreenSpaceDrawQuad.Width * 0.21f))); + + AddAssert("object 2 snapped to 1", () => + { + var objects = EditorBeatmap.HitObjects; + + var first = (OsuHitObject)objects.First(); + var second = (OsuHitObject)objects.ElementAt(1); + + return Precision.AlmostEquals(first.EndPosition, second.Position); + }); + + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + } } } From df3844cdbb833efc609355f1104c5983b1d00d7b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 12:50:37 +0900 Subject: [PATCH 179/394] Add failing tests for pooling scrolling playfield --- .../TestSceneDrawableScrollingRuleset.cs | 182 ++++++++++++++++-- 1 file changed, 165 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 1a1babb4a8..ff3d152090 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -21,13 +21,13 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osuTK; using osuTK.Graphics; +using JetBrains.Annotations; namespace osu.Game.Tests.Visual.Gameplay { @@ -46,6 +46,65 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUp] public void Setup() => Schedule(() => testClock.CurrentTime = 0); + [Test] + public void TestHitObjectPooling() + { + var beatmap = createBeatmap(_ => new TestPooledHitObject()); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); + createTest(beatmap); + + assertPosition(0, 0f); + assertDead(3); + + setTime(3 * time_range); + assertPosition(3, 0f); + assertDead(0); + + setTime(0 * time_range); + assertPosition(0, 0f); + assertDead(3); + } + + [TestCase("pooled")] + [TestCase("non-pooled")] + public void TestNestedHitObject(string pooled) + { + var beatmap = createBeatmap(i => + { + var h = pooled == "pooled" ? new TestPooledParentHitObject() : new TestParentHitObject(); + h.Duration = 300; + h.ChildTimeOffset = i % 3 * 100; + return h; + }); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); + createTest(beatmap); + + assertPosition(0, 0f); + assertHeight(0); + assertChildPosition(0); + + setTime(5 * time_range); + assertPosition(5, 0f); + assertHeight(5); + assertChildPosition(5); + } + + private void assertDead(int index) => AddAssert($"hitobject {index} is dead", () => getDrawableHitObject(index) == null); + + private void assertHeight(int index) => AddAssert($"hitobject {index} height", () => + { + var d = getDrawableHitObject(index); + return d != null && Precision.AlmostEquals(d.DrawHeight, yScale * (float)(d.HitObject.Duration / time_range), 0.1f); + }); + + private void assertChildPosition(int index) => AddAssert($"hitobject {index} child position", () => + { + var d = getDrawableHitObject(index); + return d is DrawableTestParentHitObject && Precision.AlmostEquals( + d.NestedHitObjects.First().DrawPosition.Y, + yScale * (float)((TestParentHitObject)d.HitObject).ChildTimeOffset / time_range, 0.1f); + }); + [Test] public void TestRelativeBeatLengthScaleSingleTimingPoint() { @@ -147,8 +206,21 @@ namespace osu.Game.Tests.Visual.Gameplay assertPosition(1, 1); } + /// + /// Get a corresponding to the 'th . + /// When a pooling is used and the hit object is not alive, `null` is returned. + /// + [CanBeNull] + private DrawableTestHitObject getDrawableHitObject(int index) + { + var hitObject = drawableRuleset.Beatmap.HitObjects.ElementAt(index); + return (DrawableTestHitObject)drawableRuleset.Playfield.HitObjectContainer.Objects.FirstOrDefault(obj => obj.HitObject == hitObject); + } + + private float yScale => drawableRuleset.Playfield.HitObjectContainer.DrawHeight; + private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}", - () => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY)); + () => Precision.AlmostEquals(getDrawableHitObject(index)?.DrawPosition.Y ?? -1, yScale * relativeY)); private void setTime(double time) { @@ -160,12 +232,16 @@ namespace osu.Game.Tests.Visual.Gameplay /// The hitobjects are spaced milliseconds apart. /// /// The . - private IBeatmap createBeatmap() + private IBeatmap createBeatmap(Func createAction = null) { - var beatmap = new Beatmap { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } }; + var beatmap = new Beatmap { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } }; for (int i = 0; i < 10; i++) - beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range }); + { + var h = createAction?.Invoke(i) ?? new TestHitObject(); + h.StartTime = i * time_range; + beatmap.HitObjects.Add(h); + } return beatmap; } @@ -225,7 +301,14 @@ namespace osu.Game.Tests.Visual.Gameplay TimeRange.Value = time_range; } - public override DrawableHitObject CreateDrawableRepresentation(TestHitObject h) => new DrawableTestHitObject(h); + public override DrawableHitObject CreateDrawableRepresentation(TestHitObject h) => + h switch + { + TestPooledHitObject _ => null, + TestPooledParentHitObject _ => null, + TestParentHitObject p => new DrawableTestParentHitObject(p), + _ => new DrawableTestHitObject(h), + }; protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager(); @@ -265,6 +348,9 @@ namespace osu.Game.Tests.Visual.Gameplay } } }); + + RegisterPool(1); + RegisterPool(1); } } @@ -277,30 +363,46 @@ namespace osu.Game.Tests.Visual.Gameplay public override bool CanConvert() => true; - protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) - { - yield return new TestHitObject - { - StartTime = original.StartTime, - Duration = (original as IHasDuration)?.Duration ?? 100 - }; - } + protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) => + throw new NotImplementedException(); } #endregion #region HitObject - private class TestHitObject : ConvertHitObject, IHasDuration + private class TestHitObject : HitObject, IHasDuration { public double EndTime => StartTime + Duration; - public double Duration { get; set; } + public double Duration { get; set; } = 100; + } + + private class TestPooledHitObject : TestHitObject + { + } + + private class TestParentHitObject : TestHitObject + { + public double ChildTimeOffset; + + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) + { + AddNested(new TestHitObject { StartTime = StartTime + ChildTimeOffset }); + } + } + + private class TestPooledParentHitObject : TestParentHitObject + { + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) + { + AddNested(new TestPooledHitObject { StartTime = StartTime + ChildTimeOffset }); + } } private class DrawableTestHitObject : DrawableHitObject { - public DrawableTestHitObject(TestHitObject hitObject) + public DrawableTestHitObject([CanBeNull] TestHitObject hitObject) : base(hitObject) { Anchor = Anchor.TopCentre; @@ -326,6 +428,52 @@ namespace osu.Game.Tests.Visual.Gameplay } } + private class DrawableTestPooledHitObject : DrawableTestHitObject + { + public DrawableTestPooledHitObject() + : base(null) + { + InternalChildren[0].Colour = Color4.LightSkyBlue; + InternalChildren[1].Colour = Color4.Blue; + } + + protected override void Update() => LifetimeEnd = HitObject.EndTime; + } + + private class DrawableTestParentHitObject : DrawableTestHitObject + { + private readonly Container container; + + public DrawableTestParentHitObject([CanBeNull] TestHitObject hitObject) + : base(hitObject) + { + InternalChildren[0].Colour = Color4.LightYellow; + InternalChildren[1].Colour = Color4.Yellow; + + AddInternal(container = new Container + { + RelativeSizeAxes = Axes.Both, + }); + } + + protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) => + new DrawableTestHitObject((TestHitObject)hitObject); + + protected override void AddNestedHitObject(DrawableHitObject hitObject) => container.Add(hitObject); + + protected override void ClearNestedHitObjects() => container.Clear(false); + } + + private class DrawableTestPooledParentHitObject : DrawableTestParentHitObject + { + public DrawableTestPooledParentHitObject() + : base(null) + { + InternalChildren[0].Colour = Color4.LightSeaGreen; + InternalChildren[1].Colour = Color4.Green; + } + } + #endregion } } From 5c743adbae77f5447d35be352aa1c87834f18f93 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 12:57:39 +0900 Subject: [PATCH 180/394] Support hit object pooling scrolling playfield --- .../Scrolling/ScrollingHitObjectContainer.cs | 40 ++++--------------- .../UI/Scrolling/ScrollingPlayfield.cs | 6 +++ 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index bf64175468..802204900b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -48,33 +48,8 @@ namespace osu.Game.Rulesets.UI.Scrolling timeRange.ValueChanged += _ => layoutCache.Invalidate(); } - public override void Add(DrawableHitObject hitObject) - { - combinedObjCache.Invalidate(); - hitObject.DefaultsApplied += onDefaultsApplied; - base.Add(hitObject); - } - - public override bool Remove(DrawableHitObject hitObject) - { - var result = base.Remove(hitObject); - - if (result) - { - combinedObjCache.Invalidate(); - hitObjectInitialStateCache.Remove(hitObject); - - hitObject.DefaultsApplied -= onDefaultsApplied; - } - - return result; - } - public override void Clear(bool disposeChildren = true) { - foreach (var h in Objects) - h.DefaultsApplied -= onDefaultsApplied; - base.Clear(disposeChildren); combinedObjCache.Invalidate(); @@ -173,18 +148,19 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - private void onDefaultsApplied(DrawableHitObject drawableObject) + /// + /// Invalidate the cache of the layout of this hit object. + /// + public void InvalidateDrawableHitObject(DrawableHitObject drawableObject) { - // The cache may not exist if the hitobject state hasn't been computed yet (e.g. if the hitobject was added + defaults applied in the same frame). - // In such a case, combinedObjCache will take care of updating the hitobject. if (hitObjectInitialStateCache.TryGetValue(drawableObject, out var state)) - { - combinedObjCache.Invalidate(); state.Cache.Invalidate(); - } + + combinedObjCache.Invalidate(); } - private float scrollLength; + // Use a nonzero value to prevent infinite results + private float scrollLength = 1; protected override void Update() { diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index 9dac3f4de1..9b21a3f0a9 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -24,6 +24,12 @@ namespace osu.Game.Rulesets.UI.Scrolling Direction.BindTo(ScrollingInfo.Direction); } + protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject) + { + drawableHitObject.HitObjectApplied += + ((ScrollingHitObjectContainer)HitObjectContainer).InvalidateDrawableHitObject; + } + /// /// Given a position in screen space, return the time within this column. /// From 8f39b54e58dbd673a124875da57935acd6a7a7c0 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 13:34:09 +0900 Subject: [PATCH 181/394] Simplify ScrollingHitObjectContainer logic --- .../TestSceneDrawableScrollingRuleset.cs | 2 +- .../Scrolling/ScrollingHitObjectContainer.cs | 76 +++++-------------- 2 files changed, 22 insertions(+), 56 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index ff3d152090..cebe0394c7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -445,7 +445,7 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly Container container; public DrawableTestParentHitObject([CanBeNull] TestHitObject hitObject) - : base(hitObject) + : base(hitObject) { InternalChildren[0].Colour = Color4.LightYellow; InternalChildren[1].Colour = Color4.Yellow; diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 802204900b..11a7665f0f 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -2,13 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Layout; -using osu.Framework.Threading; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -19,7 +16,6 @@ namespace osu.Game.Rulesets.UI.Scrolling { private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); - private readonly Dictionary hitObjectInitialStateCache = new Dictionary(); [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -27,9 +23,7 @@ namespace osu.Game.Rulesets.UI.Scrolling // Responds to changes in the layout. When the layout changes, all hit object states must be recomputed. private readonly LayoutValue layoutCache = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo); - // A combined cache across all hit object states to reduce per-update iterations. - // When invalidated, one or more (but not necessarily all) hitobject states must be re-validated. - private readonly Cached combinedObjCache = new Cached(); + private readonly HashSet invalidHitObjects = new HashSet(); public ScrollingHitObjectContainer() { @@ -52,8 +46,7 @@ namespace osu.Game.Rulesets.UI.Scrolling { base.Clear(disposeChildren); - combinedObjCache.Invalidate(); - hitObjectInitialStateCache.Clear(); + invalidHitObjects.Clear(); } /// @@ -150,17 +143,18 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// Invalidate the cache of the layout of this hit object. + /// A hit object should be invalidated after all its nested hit objects are invalidated. /// public void InvalidateDrawableHitObject(DrawableHitObject drawableObject) { - if (hitObjectInitialStateCache.TryGetValue(drawableObject, out var state)) - state.Cache.Invalidate(); + invalidHitObjects.Add(drawableObject); - combinedObjCache.Invalidate(); + // Remove children as nested hit objects will be recursively updated. + foreach (var nested in drawableObject.NestedHitObjects) + invalidHitObjects.Remove(nested); } - // Use a nonzero value to prevent infinite results - private float scrollLength = 1; + private float scrollLength; protected override void Update() { @@ -168,17 +162,11 @@ namespace osu.Game.Rulesets.UI.Scrolling if (!layoutCache.IsValid) { - foreach (var state in hitObjectInitialStateCache.Values) - state.Cache.Invalidate(); - combinedObjCache.Invalidate(); + foreach (var obj in Objects) + invalidHitObjects.Add(obj); scrollingInfo.Algorithm.Reset(); - layoutCache.Validate(); - } - - if (!combinedObjCache.IsValid) - { switch (direction.Value) { case ScrollingDirection.Up: @@ -191,24 +179,16 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } - foreach (var obj in Objects) - { - if (!hitObjectInitialStateCache.TryGetValue(obj, out var state)) - state = hitObjectInitialStateCache[obj] = new InitialState(new Cached()); - - if (state.Cache.IsValid) - continue; - - state.ScheduledComputation?.Cancel(); - state.ScheduledComputation = computeInitialStateRecursive(obj); - - computeLifetimeStartRecursive(obj); - - state.Cache.Validate(); - } - - combinedObjCache.Validate(); + layoutCache.Validate(); } + + foreach (var obj in invalidHitObjects) + { + computeInitialStateRecursive(obj); + computeLifetimeStartRecursive(obj); + } + + invalidHitObjects.Clear(); } private void computeLifetimeStartRecursive(DrawableHitObject hitObject) @@ -247,7 +227,7 @@ namespace osu.Game.Rulesets.UI.Scrolling return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength); } - private ScheduledDelegate computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() => + private void computeInitialStateRecursive(DrawableHitObject hitObject) { if (hitObject.HitObject is IHasDuration e) { @@ -272,7 +252,7 @@ namespace osu.Game.Rulesets.UI.Scrolling // Nested hitobjects don't need to scroll, but they do need accurate positions updatePosition(obj, hitObject.HitObject.StartTime); } - }); + } protected override void UpdateAfterChildrenLife() { @@ -304,19 +284,5 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } } - - private class InitialState - { - [NotNull] - public readonly Cached Cache; - - [CanBeNull] - public ScheduledDelegate ScheduledComputation; - - public InitialState(Cached cache) - { - Cache = cache; - } - } } } From cabc8aa63b4b3e3cb7f59046054f8b87071b651a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 13:57:20 +0900 Subject: [PATCH 182/394] Revert "Simplify ScrollingHitObjectContainer logic" This reverts commit b4cc39149c117e6a0e95ee917a67cec8ba723d06. --- .../TestSceneDrawableScrollingRuleset.cs | 2 +- .../Scrolling/ScrollingHitObjectContainer.cs | 74 ++++++++++++++----- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index cebe0394c7..ff3d152090 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -445,7 +445,7 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly Container container; public DrawableTestParentHitObject([CanBeNull] TestHitObject hitObject) - : base(hitObject) + : base(hitObject) { InternalChildren[0].Colour = Color4.LightYellow; InternalChildren[1].Colour = Color4.Yellow; diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 11a7665f0f..802204900b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -2,10 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Layout; +using osu.Framework.Threading; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -16,6 +19,7 @@ namespace osu.Game.Rulesets.UI.Scrolling { private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); + private readonly Dictionary hitObjectInitialStateCache = new Dictionary(); [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -23,7 +27,9 @@ namespace osu.Game.Rulesets.UI.Scrolling // Responds to changes in the layout. When the layout changes, all hit object states must be recomputed. private readonly LayoutValue layoutCache = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo); - private readonly HashSet invalidHitObjects = new HashSet(); + // A combined cache across all hit object states to reduce per-update iterations. + // When invalidated, one or more (but not necessarily all) hitobject states must be re-validated. + private readonly Cached combinedObjCache = new Cached(); public ScrollingHitObjectContainer() { @@ -46,7 +52,8 @@ namespace osu.Game.Rulesets.UI.Scrolling { base.Clear(disposeChildren); - invalidHitObjects.Clear(); + combinedObjCache.Invalidate(); + hitObjectInitialStateCache.Clear(); } /// @@ -143,18 +150,17 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// Invalidate the cache of the layout of this hit object. - /// A hit object should be invalidated after all its nested hit objects are invalidated. /// public void InvalidateDrawableHitObject(DrawableHitObject drawableObject) { - invalidHitObjects.Add(drawableObject); + if (hitObjectInitialStateCache.TryGetValue(drawableObject, out var state)) + state.Cache.Invalidate(); - // Remove children as nested hit objects will be recursively updated. - foreach (var nested in drawableObject.NestedHitObjects) - invalidHitObjects.Remove(nested); + combinedObjCache.Invalidate(); } - private float scrollLength; + // Use a nonzero value to prevent infinite results + private float scrollLength = 1; protected override void Update() { @@ -162,11 +168,17 @@ namespace osu.Game.Rulesets.UI.Scrolling if (!layoutCache.IsValid) { - foreach (var obj in Objects) - invalidHitObjects.Add(obj); + foreach (var state in hitObjectInitialStateCache.Values) + state.Cache.Invalidate(); + combinedObjCache.Invalidate(); scrollingInfo.Algorithm.Reset(); + layoutCache.Validate(); + } + + if (!combinedObjCache.IsValid) + { switch (direction.Value) { case ScrollingDirection.Up: @@ -179,16 +191,24 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } - layoutCache.Validate(); - } + foreach (var obj in Objects) + { + if (!hitObjectInitialStateCache.TryGetValue(obj, out var state)) + state = hitObjectInitialStateCache[obj] = new InitialState(new Cached()); - foreach (var obj in invalidHitObjects) - { - computeInitialStateRecursive(obj); - computeLifetimeStartRecursive(obj); - } + if (state.Cache.IsValid) + continue; - invalidHitObjects.Clear(); + state.ScheduledComputation?.Cancel(); + state.ScheduledComputation = computeInitialStateRecursive(obj); + + computeLifetimeStartRecursive(obj); + + state.Cache.Validate(); + } + + combinedObjCache.Validate(); + } } private void computeLifetimeStartRecursive(DrawableHitObject hitObject) @@ -227,7 +247,7 @@ namespace osu.Game.Rulesets.UI.Scrolling return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength); } - private void computeInitialStateRecursive(DrawableHitObject hitObject) + private ScheduledDelegate computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() => { if (hitObject.HitObject is IHasDuration e) { @@ -252,7 +272,7 @@ namespace osu.Game.Rulesets.UI.Scrolling // Nested hitobjects don't need to scroll, but they do need accurate positions updatePosition(obj, hitObject.HitObject.StartTime); } - } + }); protected override void UpdateAfterChildrenLife() { @@ -284,5 +304,19 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } } + + private class InitialState + { + [NotNull] + public readonly Cached Cache; + + [CanBeNull] + public ScheduledDelegate ScheduledComputation; + + public InitialState(Cached cache) + { + Cache = cache; + } + } } } From ec92545d7a4a2270d90682a3108c1936164d8e45 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 14:13:57 +0900 Subject: [PATCH 183/394] fix indent --- .../Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index ff3d152090..cebe0394c7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -445,7 +445,7 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly Container container; public DrawableTestParentHitObject([CanBeNull] TestHitObject hitObject) - : base(hitObject) + : base(hitObject) { InternalChildren[0].Colour = Color4.LightYellow; InternalChildren[1].Colour = Color4.Yellow; From ce57e8ddfb883e16dc99bfea54145c48b22a71a7 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 16:06:01 +0900 Subject: [PATCH 184/394] Separate Lifetime computation and layout update --- .../Scrolling/ScrollingHitObjectContainer.cs | 92 ++++++------------- 1 file changed, 30 insertions(+), 62 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 802204900b..1845094c44 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -2,13 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Layout; -using osu.Framework.Threading; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -19,7 +16,12 @@ namespace osu.Game.Rulesets.UI.Scrolling { private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); - private readonly Dictionary hitObjectInitialStateCache = new Dictionary(); + + // If a hit object is not in this set, the position and the size should be updated when the hit object becomes alive. + private readonly HashSet layoutComputedHitObjects = new HashSet(); + + // Used to recompute all lifetime when `layoutCache` becomes invalid + private readonly HashSet lifetimeComputedHitObjects = new HashSet(); [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -27,10 +29,6 @@ namespace osu.Game.Rulesets.UI.Scrolling // Responds to changes in the layout. When the layout changes, all hit object states must be recomputed. private readonly LayoutValue layoutCache = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo); - // A combined cache across all hit object states to reduce per-update iterations. - // When invalidated, one or more (but not necessarily all) hitobject states must be re-validated. - private readonly Cached combinedObjCache = new Cached(); - public ScrollingHitObjectContainer() { RelativeSizeAxes = Axes.Both; @@ -52,8 +50,7 @@ namespace osu.Game.Rulesets.UI.Scrolling { base.Clear(disposeChildren); - combinedObjCache.Invalidate(); - hitObjectInitialStateCache.Clear(); + layoutComputedHitObjects.Clear(); } /// @@ -150,13 +147,15 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// Invalidate the cache of the layout of this hit object. + /// A hit object should be invalidated after all its nested hit objects are invalidated. /// - public void InvalidateDrawableHitObject(DrawableHitObject drawableObject) + public void InvalidateDrawableHitObject(DrawableHitObject hitObject) { - if (hitObjectInitialStateCache.TryGetValue(drawableObject, out var state)) - state.Cache.Invalidate(); + // lifetime is computed before update + hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject); - combinedObjCache.Invalidate(); + lifetimeComputedHitObjects.Add(hitObject); + layoutComputedHitObjects.Remove(hitObject); } // Use a nonzero value to prevent infinite results @@ -168,17 +167,14 @@ namespace osu.Game.Rulesets.UI.Scrolling if (!layoutCache.IsValid) { - foreach (var state in hitObjectInitialStateCache.Values) - state.Cache.Invalidate(); - combinedObjCache.Invalidate(); + // this.Objects cannot be used as it doesn't contain nested objects + foreach (var hitObject in lifetimeComputedHitObjects) + hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject); + + layoutComputedHitObjects.Clear(); scrollingInfo.Algorithm.Reset(); - layoutCache.Validate(); - } - - if (!combinedObjCache.IsValid) - { switch (direction.Value) { case ScrollingDirection.Up: @@ -191,32 +187,18 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } - foreach (var obj in Objects) - { - if (!hitObjectInitialStateCache.TryGetValue(obj, out var state)) - state = hitObjectInitialStateCache[obj] = new InitialState(new Cached()); - - if (state.Cache.IsValid) - continue; - - state.ScheduledComputation?.Cancel(); - state.ScheduledComputation = computeInitialStateRecursive(obj); - - computeLifetimeStartRecursive(obj); - - state.Cache.Validate(); - } - - combinedObjCache.Validate(); + layoutCache.Validate(); } - } - private void computeLifetimeStartRecursive(DrawableHitObject hitObject) - { - hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject); + foreach (var obj in AliveObjects) + { + if (layoutComputedHitObjects.Contains(obj)) + continue; - foreach (var obj in hitObject.NestedHitObjects) - computeLifetimeStartRecursive(obj); + updateLayoutRecursive(obj); + + layoutComputedHitObjects.Add(obj); + } } private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject) @@ -247,7 +229,7 @@ namespace osu.Game.Rulesets.UI.Scrolling return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength); } - private ScheduledDelegate computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() => + private void updateLayoutRecursive(DrawableHitObject hitObject) { if (hitObject.HitObject is IHasDuration e) { @@ -267,12 +249,12 @@ namespace osu.Game.Rulesets.UI.Scrolling foreach (var obj in hitObject.NestedHitObjects) { - computeInitialStateRecursive(obj); + updateLayoutRecursive(obj); // Nested hitobjects don't need to scroll, but they do need accurate positions updatePosition(obj, hitObject.HitObject.StartTime); } - }); + } protected override void UpdateAfterChildrenLife() { @@ -304,19 +286,5 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } } - - private class InitialState - { - [NotNull] - public readonly Cached Cache; - - [CanBeNull] - public ScheduledDelegate ScheduledComputation; - - public InitialState(Cached cache) - { - Cache = cache; - } - } } } From d5f082e5fb2cfa609d4e9f384d29812229c0914b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 16:25:22 +0900 Subject: [PATCH 185/394] Comment about lifetime assumption --- .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 1845094c44..e750727764 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -147,11 +147,13 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// Invalidate the cache of the layout of this hit object. - /// A hit object should be invalidated after all its nested hit objects are invalidated. /// public void InvalidateDrawableHitObject(DrawableHitObject hitObject) { - // lifetime is computed before update + // Lifetime is computed once early and + // layout (Width/Height if `IHasDuration`, and nested object positions) will be computed when the object becomes alive. + // An assumption is that a hit object layout update (setting `Height` or `Width`) won't affect its lifetime. + // This is satisfied in practice because otherwise the hit object won't be aligned to its `StartTime`. hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject); lifetimeComputedHitObjects.Add(hitObject); From 7f6e4d5b217ae563b64893f9e194b7e5c2733d5e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 16:46:10 +0900 Subject: [PATCH 186/394] Delay lifetime computation until loaded --- .../Scrolling/ScrollingHitObjectContainer.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index e750727764..150ed16bab 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.UI.Scrolling private readonly HashSet layoutComputedHitObjects = new HashSet(); // Used to recompute all lifetime when `layoutCache` becomes invalid - private readonly HashSet lifetimeComputedHitObjects = new HashSet(); + private readonly HashSet allHitObjects = new HashSet(); [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -150,18 +150,19 @@ namespace osu.Game.Rulesets.UI.Scrolling /// public void InvalidateDrawableHitObject(DrawableHitObject hitObject) { - // Lifetime is computed once early and - // layout (Width/Height if `IHasDuration`, and nested object positions) will be computed when the object becomes alive. - // An assumption is that a hit object layout update (setting `Height` or `Width`) won't affect its lifetime. - // This is satisfied in practice because otherwise the hit object won't be aligned to its `StartTime`. - hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject); + // Lifetime computation is delayed to the next update because `scrollLength` may not be valid here. + // Layout computation will be delayed to when the object becomes alive. + // An assumption is that a hit object layout update (setting `Height` or `Width`) won't affect its lifetime but + // this is satisfied in practice because otherwise the hit object won't be aligned to its `StartTime`. + + layoutCache.Invalidate(); + + allHitObjects.Add(hitObject); - lifetimeComputedHitObjects.Add(hitObject); layoutComputedHitObjects.Remove(hitObject); } - // Use a nonzero value to prevent infinite results - private float scrollLength = 1; + private float scrollLength; protected override void Update() { @@ -170,7 +171,7 @@ namespace osu.Game.Rulesets.UI.Scrolling if (!layoutCache.IsValid) { // this.Objects cannot be used as it doesn't contain nested objects - foreach (var hitObject in lifetimeComputedHitObjects) + foreach (var hitObject in allHitObjects) hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject); layoutComputedHitObjects.Clear(); From e34a2051044d2901df39328051a4dbdadd094802 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 18:52:15 +0900 Subject: [PATCH 187/394] Rewrite hit object management, take three --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 8 +-- osu.Game/Rulesets/UI/Playfield.cs | 4 +- .../Scrolling/ScrollingHitObjectContainer.cs | 63 ++++++++++++------- .../UI/Scrolling/ScrollingPlayfield.cs | 6 -- 4 files changed, 45 insertions(+), 36 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 5fbda305c8..b10a6efaf2 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.UI /// /// If this uses pooled objects, this represents the time when the s become alive. /// - internal event Action HitObjectUsageBegan; + internal event Action HitObjectUsageBegan; /// /// Invoked when a becomes unused by a . @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.UI /// /// If this uses pooled objects, this represents the time when the s become dead. /// - internal event Action HitObjectUsageFinished; + internal event Action HitObjectUsageFinished; /// /// The amount of time prior to the current time within which s should be considered alive. @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.UI bindStartTime(drawable); AddInternal(drawableMap[entry] = drawable, false); - HitObjectUsageBegan?.Invoke(entry.HitObject); + HitObjectUsageBegan?.Invoke(drawable); } private void removeDrawable(HitObjectLifetimeEntry entry) @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.UI unbindStartTime(drawable); RemoveInternal(drawable); - HitObjectUsageFinished?.Invoke(entry.HitObject); + HitObjectUsageFinished?.Invoke(drawable); } #endregion diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 82ec653f31..e27ab7fda5 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -91,8 +91,8 @@ namespace osu.Game.Rulesets.UI { h.NewResult += (d, r) => NewResult?.Invoke(d, r); h.RevertResult += (d, r) => RevertResult?.Invoke(d, r); - h.HitObjectUsageBegan += o => HitObjectUsageBegan?.Invoke(o); - h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o); + h.HitObjectUsageBegan += o => HitObjectUsageBegan?.Invoke(o.HitObject); + h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o.HitObject); })); } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 150ed16bab..c8afe76f19 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -17,11 +17,16 @@ namespace osu.Game.Rulesets.UI.Scrolling private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); - // If a hit object is not in this set, the position and the size should be updated when the hit object becomes alive. - private readonly HashSet layoutComputedHitObjects = new HashSet(); + // Tracks all `DrawableHitObject` (nested or not) applied a `HitObject`. + // It dynamically changes based on approximate lifetime when a pooling is used. + private readonly HashSet hitObjectApplied = new HashSet(); - // Used to recompute all lifetime when `layoutCache` becomes invalid - private readonly HashSet allHitObjects = new HashSet(); + // The lifetime of a hit object in this will be computed in next update. + private readonly HashSet toComputeLifetime = new HashSet(); + + // The layout (length if IHasDuration, and nested object positions) of a hit object *not* in this set will be computed in next updated. + // Only objects in `AliveObjects` are considered, to prevent a massive recomputation when scrolling speed or something changes. + private readonly HashSet layoutComputed = new HashSet(); [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -34,6 +39,9 @@ namespace osu.Game.Rulesets.UI.Scrolling RelativeSizeAxes = Axes.Both; AddLayout(layoutCache); + + HitObjectUsageBegan += onHitObjectUsageBegin; + HitObjectUsageFinished += onHitObjectUsageFinished; } [BackgroundDependencyLoader] @@ -50,7 +58,9 @@ namespace osu.Game.Rulesets.UI.Scrolling { base.Clear(disposeChildren); - layoutComputedHitObjects.Clear(); + hitObjectApplied.Clear(); + toComputeLifetime.Clear(); + layoutComputed.Clear(); } /// @@ -145,21 +155,20 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - /// - /// Invalidate the cache of the layout of this hit object. - /// - public void InvalidateDrawableHitObject(DrawableHitObject hitObject) + private void onHitObjectUsageBegin(DrawableHitObject hitObject) { - // Lifetime computation is delayed to the next update because `scrollLength` may not be valid here. - // Layout computation will be delayed to when the object becomes alive. - // An assumption is that a hit object layout update (setting `Height` or `Width`) won't affect its lifetime but - // this is satisfied in practice because otherwise the hit object won't be aligned to its `StartTime`. + // Lifetime computation is delayed until next update because + // when the hit object is not pooled this container is not loaded here and `scrollLength` cannot be computed. + hitObjectApplied.Add(hitObject); + toComputeLifetime.Add(hitObject); + layoutComputed.Remove(hitObject); + } - layoutCache.Invalidate(); - - allHitObjects.Add(hitObject); - - layoutComputedHitObjects.Remove(hitObject); + private void onHitObjectUsageFinished(DrawableHitObject hitObject) + { + hitObjectApplied.Remove(hitObject); + toComputeLifetime.Remove(hitObject); + layoutComputed.Remove(hitObject); } private float scrollLength; @@ -170,11 +179,10 @@ namespace osu.Game.Rulesets.UI.Scrolling if (!layoutCache.IsValid) { - // this.Objects cannot be used as it doesn't contain nested objects - foreach (var hitObject in allHitObjects) - hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject); + foreach (var hitObject in hitObjectApplied) + toComputeLifetime.Add(hitObject); - layoutComputedHitObjects.Clear(); + layoutComputed.Clear(); scrollingInfo.Algorithm.Reset(); @@ -193,14 +201,21 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutCache.Validate(); } + foreach (var hitObject in toComputeLifetime) + hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject); + + toComputeLifetime.Clear(); + + // An assumption is that this update won't affect lifetime, + // but this is satisfied in practice because otherwise the hit object won't be aligned to its `StartTime`. foreach (var obj in AliveObjects) { - if (layoutComputedHitObjects.Contains(obj)) + if (layoutComputed.Contains(obj)) continue; updateLayoutRecursive(obj); - layoutComputedHitObjects.Add(obj); + layoutComputed.Add(obj); } } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index 9b21a3f0a9..9dac3f4de1 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -24,12 +24,6 @@ namespace osu.Game.Rulesets.UI.Scrolling Direction.BindTo(ScrollingInfo.Direction); } - protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject) - { - drawableHitObject.HitObjectApplied += - ((ScrollingHitObjectContainer)HitObjectContainer).InvalidateDrawableHitObject; - } - /// /// Given a position in screen space, return the time within this column. /// From b8a5cd94f741f98453bdd8118e95dc40369012f4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 19:46:57 +0900 Subject: [PATCH 188/394] Invoke HitObjectUsageFinished before removal --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index b10a6efaf2..fb91108605 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -129,10 +129,10 @@ namespace osu.Game.Rulesets.UI drawableMap.Remove(entry); + HitObjectUsageFinished?.Invoke(drawable); + unbindStartTime(drawable); RemoveInternal(drawable); - - HitObjectUsageFinished?.Invoke(drawable); } #endregion From 916a313f1965ae52872001bd6b3e511854d3368d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 19:13:46 +0900 Subject: [PATCH 189/394] Rename PalpableDrawable -> DrawablePalpable --- .../Objects/Drawables/DrawableCatchHitObject.cs | 4 ++-- osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs | 2 +- osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 07f1f79243..7fbc79e4b5 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -13,11 +13,11 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public abstract class PalpableDrawableCatchHitObject : DrawableCatchHitObject + public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject { protected Container ScaleContainer { get; private set; } - protected PalpableDrawableCatchHitObject(CatchHitObject hitObject) + protected DrawablePalpableCatchHitObject(CatchHitObject hitObject) : base(hitObject) { Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index 9db64eba6e..37a05b5d76 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -8,7 +8,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public class DrawableDroplet : PalpableDrawableCatchHitObject + public class DrawableDroplet : DrawablePalpableCatchHitObject { public override bool StaysOnPlate => false; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index f87c8866b1..b000a728c1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -8,7 +8,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public class DrawableFruit : PalpableDrawableCatchHitObject + public class DrawableFruit : DrawablePalpableCatchHitObject { public DrawableFruit(CatchHitObject h) : base(h) From 4f7aa7e54187fa5b6371499b7d963862a37fb678 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 19:16:03 +0900 Subject: [PATCH 190/394] Move Palpable* to separate files --- .../Objects/CatchHitObject.cs | 8 ---- .../Drawables/DrawableCatchHitObject.cs | 37 ---------------- .../DrawablePalpableCatchHitObject.cs | 44 +++++++++++++++++++ .../Objects/PalpableCatchHitObject.cs | 13 ++++++ 4 files changed, 57 insertions(+), 45 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs create mode 100644 osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 5985ec9b68..21bb026bae 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -105,14 +105,6 @@ namespace osu.Game.Rulesets.Catch.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; } - /// - /// Represents a single object that can be caught by the catcher. - /// - public abstract class PalpableCatchHitObject : CatchHitObject - { - public override bool CanBePlated => true; - } - public enum FruitVisualRepresentation { Pear, diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 7fbc79e4b5..4cbc998447 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -2,49 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Drawables; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject - { - protected Container ScaleContainer { get; private set; } - - protected DrawablePalpableCatchHitObject(CatchHitObject hitObject) - : base(hitObject) - { - Origin = Anchor.Centre; - Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2); - Masking = false; - } - - [BackgroundDependencyLoader] - private void load() - { - AddRangeInternal(new Drawable[] - { - ScaleContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - } - }); - - ScaleContainer.Scale = new Vector2(HitObject.Scale); - } - - protected override Color4 GetComboColour(IReadOnlyList comboColours) => - comboColours[(HitObject.IndexInBeatmap + 1) % comboColours.Count]; - } - public abstract class DrawableCatchHitObject : DrawableHitObject { protected override double InitialLifetimeOffset => HitObject.TimePreempt; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs new file mode 100644 index 0000000000..3e843d60c1 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables +{ + public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject + { + protected Container ScaleContainer { get; private set; } + + protected DrawablePalpableCatchHitObject(CatchHitObject hitObject) + : base(hitObject) + { + Origin = Anchor.Centre; + Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2); + Masking = false; + } + + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + ScaleContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + } + }); + + ScaleContainer.Scale = new Vector2(HitObject.Scale); + } + + protected override Color4 GetComboColour(IReadOnlyList comboColours) => + comboColours[(HitObject.IndexInBeatmap + 1) % comboColours.Count]; + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs new file mode 100644 index 0000000000..3a0cbe3ecd --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Catch.Objects +{ + /// + /// Represents a single object that can be caught by the catcher. + /// + public abstract class PalpableCatchHitObject : CatchHitObject + { + public override bool CanBePlated => true; + } +} From ab7251d742ec03ae9a9feb082ae2867209e0b955 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 19:57:37 +0900 Subject: [PATCH 191/394] Move members to `PalpableCatchHitObject` --- .../TestSceneFruitObjects.cs | 2 +- .../Beatmaps/CatchBeatmapProcessor.cs | 22 ++++++++-------- .../Preprocessing/CatchDifficultyHitObject.cs | 4 +-- .../Objects/CatchHitObject.cs | 22 ---------------- .../Drawables/DrawableCatchHitObject.cs | 2 -- .../Objects/Drawables/DrawableFruit.cs | 2 ++ .../DrawablePalpableCatchHitObject.cs | 26 +++++++++---------- .../Objects/Drawables/DropletPiece.cs | 2 +- .../Objects/Drawables/FruitPiece.cs | 4 +-- .../Objects/PalpableCatchHitObject.cs | 20 +++++++++++++- .../Replays/CatchAutoGenerator.cs | 17 ++++-------- .../Skinning/LegacyFruitPiece.cs | 3 ++- osu.Game.Rulesets.Catch/UI/Catcher.cs | 6 ++--- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 12 ++++----- 14 files changed, 66 insertions(+), 78 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index 89063319d6..e8ecd2ca1b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Tests hitObject.Scale = 1.5f; if (hyperdash) - hitObject.HyperDashTarget = new Banana(); + ((PalpableCatchHitObject)hitObject).HyperDashTarget = new Banana(); d.Anchor = Anchor.Centre; d.RelativePositionAxes = Axes.None; diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index a08c5b6fb1..00ce9ea8c2 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -5,11 +5,11 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.MathUtils; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Catch.MathUtils; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Beatmaps { @@ -192,24 +192,24 @@ namespace osu.Game.Rulesets.Catch.Beatmaps private static void initialiseHyperDash(IBeatmap beatmap) { - List objectWithDroplets = new List(); + List palpableObjects = new List(); foreach (var currentObject in beatmap.HitObjects) { if (currentObject is Fruit fruitObject) - objectWithDroplets.Add(fruitObject); + palpableObjects.Add(fruitObject); if (currentObject is JuiceStream) { - foreach (var currentJuiceElement in currentObject.NestedHitObjects) + foreach (var juice in currentObject.NestedHitObjects) { - if (!(currentJuiceElement is TinyDroplet)) - objectWithDroplets.Add((CatchHitObject)currentJuiceElement); + if (juice is PalpableCatchHitObject palpableObject && !(juice is TinyDroplet)) + palpableObjects.Add(palpableObject); } } } - objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); + palpableObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2; @@ -221,10 +221,10 @@ namespace osu.Game.Rulesets.Catch.Beatmaps int lastDirection = 0; double lastExcess = halfCatcherWidth; - for (int i = 0; i < objectWithDroplets.Count - 1; i++) + for (int i = 0; i < palpableObjects.Count - 1; i++) { - CatchHitObject currentObject = objectWithDroplets[i]; - CatchHitObject nextObject = objectWithDroplets[i + 1]; + var currentObject = palpableObjects[i]; + var nextObject = palpableObjects[i + 1]; // Reset variables in-case values have changed (e.g. after applying HR) currentObject.HyperDashTarget = null; diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index 3e21b8fbaf..dcd410e08f 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -12,9 +12,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing { private const float normalized_hitobject_radius = 41.0f; - public new CatchHitObject BaseObject => (CatchHitObject)base.BaseObject; + public new PalpableCatchHitObject BaseObject => (PalpableCatchHitObject)base.BaseObject; - public new CatchHitObject LastObject => (CatchHitObject)base.LastObject; + public new PalpableCatchHitObject LastObject => (PalpableCatchHitObject)base.LastObject; public readonly float NormalizedPosition; public readonly float LastNormalizedPosition; diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 21bb026bae..ccd2422381 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -27,11 +27,6 @@ namespace osu.Game.Rulesets.Catch.Objects set => x = value; } - /// - /// Whether this object can be placed on the catcher's plate. - /// - public virtual bool CanBePlated => false; - /// /// A random offset applied to , set by the . /// @@ -63,13 +58,6 @@ namespace osu.Game.Rulesets.Catch.Objects set => ComboIndexBindable.Value = value; } - /// - /// Difference between the distance to the next object - /// and the distance that would have triggered a hyper dash. - /// A value close to 0 indicates a difficult jump (for difficulty calculation). - /// - public float DistanceToHyperDash { get; set; } - public Bindable LastInComboBindable { get; } = new Bindable(); /// @@ -83,16 +71,6 @@ namespace osu.Game.Rulesets.Catch.Objects public float Scale { get; set; } = 1; - /// - /// Whether this fruit can initiate a hyperdash. - /// - public bool HyperDash => HyperDashTarget != null; - - /// - /// The target fruit if we are to initiate a hyperdash. - /// - public CatchHitObject HyperDashTarget; - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 4cbc998447..f9f534f9ab 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -12,8 +12,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { protected override double InitialLifetimeOffset => HitObject.TimePreempt; - public virtual bool StaysOnPlate => HitObject.CanBePlated; - public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale; protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index b000a728c1..a2fa79965f 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -10,6 +10,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { public class DrawableFruit : DrawablePalpableCatchHitObject { + public override bool StaysOnPlate => true; + public DrawableFruit(CatchHitObject h) : base(h) { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs index 3e843d60c1..539dbb52d1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs @@ -12,29 +12,27 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject { - protected Container ScaleContainer { get; private set; } + public virtual bool StaysOnPlate => true; - protected DrawablePalpableCatchHitObject(CatchHitObject hitObject) - : base(hitObject) + protected readonly Container ScaleContainer; + + protected DrawablePalpableCatchHitObject(CatchHitObject h) + : base(h) { Origin = Anchor.Centre; Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2); - Masking = false; + + AddInternal(ScaleContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + }); } [BackgroundDependencyLoader] private void load() { - AddRangeInternal(new Drawable[] - { - ScaleContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - } - }); - ScaleContainer.Scale = new Vector2(HitObject.Scale); } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs index c2499446fa..61e6187611 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables private void load(DrawableHitObject drawableObject) { DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject; - var hitObject = drawableCatchObject.HitObject; + var hitObject = (PalpableCatchHitObject)drawableCatchObject.HitObject; InternalChild = new Pulp { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs index 4bffdab3d8..b14e37d138 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public const float RADIUS_ADJUST = 1.1f; private Circle border; - private CatchHitObject hitObject; + private PalpableCatchHitObject hitObject; public FruitPiece() { @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables private void load(DrawableHitObject drawableObject) { DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject; - hitObject = drawableCatchObject.HitObject; + hitObject = (PalpableCatchHitObject)drawableCatchObject.HitObject; AddRangeInternal(new[] { diff --git a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs index 3a0cbe3ecd..5e35b9ea12 100644 --- a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs @@ -5,9 +5,27 @@ namespace osu.Game.Rulesets.Catch.Objects { /// /// Represents a single object that can be caught by the catcher. + /// This includes normal fruits, droplets, and bananas but excludes objects that acts only as a container of nested hit objects. /// public abstract class PalpableCatchHitObject : CatchHitObject { - public override bool CanBePlated => true; + /// + /// Difference between the distance to the next object + /// and the distance that would have triggered a hyper dash. + /// A value close to 0 indicates a difficult jump (for difficulty calculation). + /// + public float DistanceToHyperDash { get; set; } + + /// + /// Whether this fruit can initiate a hyperdash. + /// + public bool HyperDash => HyperDashTarget != null; + + /// + /// The target fruit if we are to initiate a hyperdash. + /// + public CatchHitObject HyperDashTarget; + + public virtual bool StaysOnPlate => true; } } diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index a4f54bfe82..dfc81ee8d9 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Replays float lastPosition = CatchPlayfield.CENTER_X; double lastTime = 0; - void moveToNext(CatchHitObject h) + void moveToNext(PalpableCatchHitObject h) { float positionChange = Math.Abs(lastPosition - h.X); double timeAvailable = h.StartTime - lastTime; @@ -101,23 +101,16 @@ namespace osu.Game.Rulesets.Catch.Replays foreach (var obj in Beatmap.HitObjects) { - switch (obj) + if (obj is PalpableCatchHitObject palpableObject) { - case Fruit _: - moveToNext(obj); - break; + moveToNext(palpableObject); } foreach (var nestedObj in obj.NestedHitObjects.Cast()) { - switch (nestedObj) + if (nestedObj is PalpableCatchHitObject palpableNestedObject) { - case Banana _: - case TinyDroplet _: - case Droplet _: - case Fruit _: - moveToNext(nestedObj); - break; + moveToNext(palpableNestedObject); } } } diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index 381d066750..00ab20152a 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Drawables; @@ -51,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Skinning }, }; - if (drawableCatchObject.HitObject.HyperDash) + if (((PalpableCatchHitObject)drawableCatchObject.HitObject).HyperDash) { var hyperDash = new Sprite { diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index a221ca7966..0f0b9df76e 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -220,11 +220,11 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Let the catcher attempt to catch a fruit. /// - /// The fruit to catch. + /// The fruit to catch. /// Whether the catch is possible. - public bool AttemptCatch(CatchHitObject fruit) + public bool AttemptCatch(CatchHitObject hitObject) { - if (!fruit.CanBePlated) + if (!(hitObject is PalpableCatchHitObject fruit)) return false; var halfCatchWidth = catchWidth * 0.5f; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 5e794a76aa..70739673a9 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.UI }; } - public void OnNewResult(DrawableCatchHitObject fruit, JudgementResult result) + public void OnNewResult(DrawableCatchHitObject hitObject, JudgementResult result) { if (!result.Type.IsScorable()) return; @@ -69,15 +69,15 @@ namespace osu.Game.Rulesets.Catch.UI lastPlateableFruit.OnLoadComplete += _ => action(); } - if (result.IsHit && fruit.HitObject.CanBePlated) + if (result.IsHit && hitObject.HitObject is PalpableCatchHitObject fruit) { // create a new (cloned) fruit to stay on the plate. the original is faded out immediately. - var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject); + var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit); if (caughtFruit == null) return; caughtFruit.RelativePositionAxes = Axes.None; - caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0); + caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(hitObject.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0); caughtFruit.IsOnPlate = true; caughtFruit.Anchor = Anchor.TopCentre; @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.UI runAfterLoaded(() => MovableCatcher.Explode(caughtFruit)); } - if (fruit.HitObject.LastInCombo) + if (hitObject.HitObject.LastInCombo) { if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result)) runAfterLoaded(() => MovableCatcher.Explode()); @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Catch.UI MovableCatcher.Drop(); } - comboDisplay.OnNewResult(fruit, result); + comboDisplay.OnNewResult(hitObject, result); } public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result) From 3c3229ac4b262ec4856d8ae0c43ed561f2b77a6a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 25 Nov 2020 07:59:45 +0900 Subject: [PATCH 192/394] Remove redundant `StaysOnPlate` --- osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs | 2 -- osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs | 4 +--- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index a2fa79965f..b000a728c1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -10,8 +10,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { public class DrawableFruit : DrawablePalpableCatchHitObject { - public override bool StaysOnPlate => true; - public DrawableFruit(CatchHitObject h) : base(h) { diff --git a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs index 5e35b9ea12..bdc7dcc1fe 100644 --- a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Catch.Objects @@ -25,7 +25,5 @@ namespace osu.Game.Rulesets.Catch.Objects /// The target fruit if we are to initiate a hyperdash. /// public CatchHitObject HyperDashTarget; - - public virtual bool StaysOnPlate => true; } } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 70739673a9..ad79a23279 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -69,10 +69,10 @@ namespace osu.Game.Rulesets.Catch.UI lastPlateableFruit.OnLoadComplete += _ => action(); } - if (result.IsHit && hitObject.HitObject is PalpableCatchHitObject fruit) + if (result.IsHit && hitObject is DrawablePalpableCatchHitObject fruit) { // create a new (cloned) fruit to stay on the plate. the original is faded out immediately. - var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit); + var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject); if (caughtFruit == null) return; From 6e55eb2090c82bd8f1efce579aeebeb17eaadfe0 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 25 Nov 2020 08:00:11 +0900 Subject: [PATCH 193/394] Fix and add comments --- .../Objects/Drawables/DrawablePalpableCatchHitObject.cs | 3 +++ osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs index 539dbb52d1..c096ea2814 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs @@ -12,6 +12,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject { + /// + /// Whether this hit object should stay on the catcher plate when the object is caught by the catcher. + /// public virtual bool StaysOnPlate => true; protected readonly Container ScaleContainer; diff --git a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs index bdc7dcc1fe..361b338b2c 100644 --- a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs @@ -1,11 +1,11 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Catch.Objects { /// /// Represents a single object that can be caught by the catcher. - /// This includes normal fruits, droplets, and bananas but excludes objects that acts only as a container of nested hit objects. + /// This includes normal fruits, droplets, and bananas but excludes objects that act only as a container of nested hit objects. /// public abstract class PalpableCatchHitObject : CatchHitObject { From 323533d94574451fb801645b3f2535c89b94ed3d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 25 Nov 2020 08:07:59 +0900 Subject: [PATCH 194/394] Add hiding Palpable HitObject property --- .../Objects/Drawables/DrawablePalpableCatchHitObject.cs | 2 ++ osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs | 5 ++--- osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs | 6 +++--- osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs | 5 ++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs index c096ea2814..935aad914e 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs @@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject { + public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject; + /// /// Whether this hit object should stay on the catcher plate when the object is caught by the catcher. /// diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs index 61e6187611..dd0723c744 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs @@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables [BackgroundDependencyLoader] private void load(DrawableHitObject drawableObject) { - DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject; - var hitObject = (PalpableCatchHitObject)drawableCatchObject.HitObject; + var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject; InternalChild = new Pulp { @@ -31,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables AccentColour = { BindTarget = drawableObject.AccentColour } }; - if (hitObject.HyperDash) + if (drawableCatchObject.HitObject.HyperDash) { AddInternal(new Container { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs index b14e37d138..f98050ae91 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs @@ -30,12 +30,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables [BackgroundDependencyLoader] private void load(DrawableHitObject drawableObject) { - DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject; - hitObject = (PalpableCatchHitObject)drawableCatchObject.HitObject; + var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject; + hitObject = drawableCatchObject.HitObject; AddRangeInternal(new[] { - getFruitFor(drawableCatchObject.HitObject.VisualRepresentation), + getFruitFor(hitObject.VisualRepresentation), border = new Circle { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index 00ab20152a..1494ef3888 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Drawables; @@ -32,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Skinning [BackgroundDependencyLoader] private void load(DrawableHitObject drawableObject, ISkinSource skin) { - DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject; + var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject; accentColour.BindTo(drawableCatchObject.AccentColour); @@ -52,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Skinning }, }; - if (((PalpableCatchHitObject)drawableCatchObject.HitObject).HyperDash) + if (drawableCatchObject.HitObject.HyperDash) { var hyperDash = new Sprite { From c46d655832b6b21bc4c7c706ecbcb49d0ea6bb88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Nov 2020 12:11:44 +0900 Subject: [PATCH 195/394] Uncomment incorrectly commented lines --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 4ed3b75f2e..da9bb8a09d 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -125,8 +125,8 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); // undo any transforms applies in ApplyMissAnimations/ApplyHitAnimations to get a sane initial state. - // ApplyTransformsAt(double.MinValue, true); - // ClearTransforms(true); + ApplyTransformsAt(double.MinValue, true); + ClearTransforms(true); LifetimeStart = Result.TimeAbsolute; From d4c6d6275e4197d81d91531d9f41e26085b8df0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Nov 2020 16:46:19 +0900 Subject: [PATCH 196/394] Fix volume not being adjustable in the editor using alt-scroll We do this in other places so I think it's fine to handle like this for now (until we come up with a better global solution). Closes #10958. --- osu.Game/Screens/Edit/Editor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 85467d3bbb..3d5c0ddad4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -375,6 +375,9 @@ namespace osu.Game.Screens.Edit protected override bool OnScroll(ScrollEvent e) { + if (e.ControlPressed || e.AltPressed || e.SuperPressed) + return false; + const double precision = 1; double scrollComponent = e.ScrollDelta.X + e.ScrollDelta.Y; From 0ddeff648d2581a64613346543ce1c5dcb202f8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Nov 2020 17:25:54 +0900 Subject: [PATCH 197/394] Fix incorrect index lookup on non-ordered selections --- .../Compose/Components/BlueprintContainer.cs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index e9f5238980..def5f396f1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -187,7 +187,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (e.Button == MouseButton.Right) return false; - if (movementBlueprint != null) + if (movementBlueprints != null) { isDraggingBlueprint = true; changeHandler?.BeginChange(); @@ -299,7 +299,7 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionBlueprints.Remove(blueprint); - if (movementBlueprint == blueprint) + if (movementBlueprints?.Contains(blueprint) == true) finishSelectionMovement(); OnBlueprintRemoved(hitObject); @@ -425,7 +425,7 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Selection Movement private Vector2[] movementBlueprintOriginalPositions; - private SelectionBlueprint movementBlueprint; + private SelectionBlueprint[] movementBlueprints; private bool isDraggingBlueprint; /// @@ -442,9 +442,8 @@ namespace osu.Game.Screens.Edit.Compose.Components return; // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject - var orderedSelection = SelectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime); - movementBlueprint = orderedSelection.First(); - movementBlueprintOriginalPositions = orderedSelection.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); + movementBlueprints = SelectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).ToArray(); + movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); } /// @@ -454,7 +453,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether a movement was active. private bool moveCurrentSelection(DragEvent e) { - if (movementBlueprint == null) + if (movementBlueprints == null) return false; if (snapProvider == null) @@ -474,7 +473,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (positionalResult.ScreenSpacePosition == testPosition) continue; // attempt to move the objects, and abort any time based snapping if we can. - if (SelectionHandler.HandleMovement(new MoveSelectionEvent(SelectionHandler.SelectedBlueprints.ElementAt(i), positionalResult.ScreenSpacePosition))) + if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], positionalResult.ScreenSpacePosition))) return true; } @@ -488,13 +487,13 @@ namespace osu.Game.Screens.Edit.Compose.Components var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); // Move the hitobjects. - if (!SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition))) + if (!SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints.First(), result.ScreenSpacePosition))) return true; if (result.Time.HasValue) { // Apply the start time at the newly snapped-to position - double offset = result.Time.Value - movementBlueprint.HitObject.StartTime; + double offset = result.Time.Value - movementBlueprints.First().HitObject.StartTime; foreach (HitObject obj in Beatmap.SelectedHitObjects) { @@ -512,11 +511,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether a movement was active. private bool finishSelectionMovement() { - if (movementBlueprint == null) + if (movementBlueprints == null) return false; movementBlueprintOriginalPositions = null; - movementBlueprint = null; + movementBlueprints = null; return true; } From 740b9fb3a08bdffc40a74a1b963ac8864f7d6a75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Nov 2020 17:33:21 +0900 Subject: [PATCH 198/394] Update test to cover non-ordered selection --- .../Editor/TestSceneObjectObjectSnap.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index d20be90001..6b532e5014 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -98,15 +98,24 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("place first object", () => InputManager.Click(MouseButton.Left)); + AddStep("increment time", () => EditorClock.SeekForward(true)); + AddStep("move mouse right", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.2f, 0))); AddStep("place second object", () => InputManager.Click(MouseButton.Left)); + AddStep("increment time", () => EditorClock.SeekForward(true)); + AddStep("move mouse down", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(0, playfield.ScreenSpaceDrawQuad.Width * 0.2f))); AddStep("place third object", () => InputManager.Click(MouseButton.Left)); AddStep("enter selection mode", () => InputManager.Key(Key.Number1)); - AddStep("select objects 2 and 3", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects.Skip(1))); + AddStep("select objects 2 and 3", () => + { + // add selection backwards to test non-sequential time ordering + EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[2]); + EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[1]); + }); AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); From c744db1b571873d05e9411c3a7fd0053fe3e2fa8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 25 Nov 2020 17:54:03 +0900 Subject: [PATCH 199/394] Rewind pooled DHOs into better states --- .../Objects/Drawables/Pieces/MainCirclePiece.cs | 10 +++++----- .../Skinning/LegacyMainCirclePiece.cs | 10 +++++----- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 12 +++++++++++- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs index bf2236c945..102166f8dd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs @@ -38,7 +38,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces }; } - private readonly IBindable state = new Bindable(); private readonly IBindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); @@ -50,7 +49,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { var drawableOsuObject = (DrawableOsuHitObject)drawableObject; - state.BindTo(drawableObject.State); accentColour.BindTo(drawableObject.AccentColour); indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable); } @@ -59,7 +57,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { base.LoadComplete(); - state.BindValueChanged(updateState, true); accentColour.BindValueChanged(colour => { explode.Colour = colour.NewValue; @@ -68,15 +65,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces }, true); indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true); + + drawableObject.ApplyCustomUpdateState += updateState; + updateState(drawableObject, drawableObject.State.Value); } - private void updateState(ValueChangedEvent state) + private void updateState(DrawableHitObject drawableObject, ArmedState state) { using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime, true)) { glow.FadeOut(400); - switch (state.NewValue) + switch (state) { case ArmedState.Hit: const double flash_in = 40; diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 1551d1c149..21af9a479e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -38,7 +38,6 @@ namespace osu.Game.Rulesets.Osu.Skinning private SkinnableSpriteText hitCircleText; - private readonly IBindable state = new Bindable(); private readonly Bindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); @@ -113,7 +112,6 @@ namespace osu.Game.Rulesets.Osu.Skinning if (overlayAboveNumber) AddInternal(hitCircleOverlay.CreateProxy()); - state.BindTo(drawableObject.State); accentColour.BindTo(drawableObject.AccentColour); indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable); @@ -137,19 +135,21 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.LoadComplete(); - state.BindValueChanged(updateState, true); accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true); if (hasNumber) indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); + + drawableObject.ApplyCustomUpdateState += updateState; + updateState(drawableObject, drawableObject.State.Value); } - private void updateState(ValueChangedEvent state) + private void updateState(DrawableHitObject drawableObject, ArmedState state) { const double legacy_fade_duration = 240; using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime, true)) { - switch (state.NewValue) + switch (state) { case ArmedState.Hit: circleSprites.FadeOut(legacy_fade_duration, Easing.Out); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 537da24e01..4c55938e49 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -259,7 +259,17 @@ namespace osu.Game.Rulesets.Objects.Drawables // If not loaded, the state update happens in LoadComplete(). Otherwise, the update is scheduled to allow for lifetime updates. if (IsLoaded) - Schedule(() => updateState(ArmedState.Idle, true)); + { + Scheduler.Add(() => + { + if (Result.IsHit) + updateState(ArmedState.Hit, true); + else if (Result.HasResult) + updateState(ArmedState.Miss, true); + else + updateState(ArmedState.Idle, true); + }); + } hasHitObjectApplied = true; } From 0414e5c5502effefb56416d58d26645683179805 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 25 Nov 2020 23:38:47 +0900 Subject: [PATCH 200/394] Add MaximumJudgementOffset to DrawableHitObject, use in more places --- .../Objects/Drawables/DrawableHoldNoteTail.cs | 2 ++ .../Objects/Drawables/DrawableOsuHitObject.cs | 8 ------- .../Objects/Drawables/DrawableSpinnerTick.cs | 12 ++++++++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 ++ .../Objects/Drawables/DrawableDrumRollTick.cs | 2 ++ .../Objects/Drawables/DrawableHitObject.cs | 24 +++++++++++-------- 6 files changed, 32 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index a4029e7893..3a00933e4d 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public void UpdateResult() => base.UpdateResult(true); + protected override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience; + protected override void CheckForResult(bool userTriggered, double timeOffset) { Debug.Assert(HitObject.HitWindows != null); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index a26db06ede..94bce53b12 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -11,7 +11,6 @@ using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -61,13 +60,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables PositionBindable.BindTo(HitObject.PositionBindable); StackHeightBindable.BindTo(HitObject.StackHeightBindable); ScaleBindable.BindTo(HitObject.ScaleBindable); - - // Manually set to reduce the number of future alive objects to a bare minimum. - LifetimeStart = HitObject.StartTime - HitObject.TimePreempt; - - // Arbitrary lifetime end to prevent past objects in idle states remaining alive in non-frame-stable contexts. - // An extra 1000ms is added to always overestimate the true lifetime, and a more exact value is set by hit transforms and the following expiry. - LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss) + 1000; } protected override void OnFree(HitObject hitObject) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index fc9a7c00e6..f37d933e11 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Rulesets.Objects.Drawables; + namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSpinnerTick : DrawableOsuHitObject @@ -17,6 +19,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { } + private DrawableSpinner drawableSpinner; + + protected override void OnParentReceived(DrawableHitObject parent) + { + base.OnParentReceived(parent); + drawableSpinner = (DrawableSpinner)parent; + } + + protected override double MaximumJudgementOffset => drawableSpinner.HitObject.Duration; + /// /// Apply a judgement result. /// diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 8ff752952c..243092d067 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -176,6 +176,8 @@ namespace osu.Game.Rulesets.Osu.UI public OsuHitObjectLifetimeEntry(HitObject hitObject) : base(hitObject) { + // Arbitrary lifetime end to prevent past objects in idle states remaining alive in non-frame-stable contexts. + LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss) + 1000; } protected override double InitialLifetimeOffset => ((OsuHitObject)HitObject).TimePreempt; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index bf44a80037..be659f6ca5 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Filled = HitObject.FirstTick }); + protected override double MaximumJudgementOffset => HitObject.HitWindow; + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (!userTriggered) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 537da24e01..eeaac0d77b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -699,6 +699,18 @@ namespace osu.Game.Rulesets.Objects.Drawables UpdateResult(false); } + /// + /// The maximum offset from the end time of at which this can be judged. + /// The time offset of will be clamped to this value during . + /// + /// Defaults to the miss window of . + /// + /// + /// + /// This does not affect the time offset provided to invocations of . + /// + protected virtual double MaximumJudgementOffset => HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? 0; + /// /// Applies the of this , notifying responders such as /// the of the . @@ -738,14 +750,7 @@ namespace osu.Game.Rulesets.Objects.Drawables $"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}])."); } - // Ensure that the judgement is given a valid time offset, because this may not get set by the caller - var endTime = HitObject.GetEndTime(); - - Result.TimeOffset = Time.Current - endTime; - - double missWindow = HitObject.HitWindows.WindowFor(HitResult.Miss); - if (missWindow > 0) - Result.TimeOffset = Math.Min(Result.TimeOffset, missWindow); + Result.TimeOffset = Math.Min(MaximumJudgementOffset, Time.Current - HitObject.GetEndTime()); if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); @@ -767,8 +772,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (Judged) return false; - var endTime = HitObject.GetEndTime(); - CheckForResult(userTriggered, Time.Current - endTime); + CheckForResult(userTriggered, Time.Current - HitObject.GetEndTime()); return Judged; } From 0817dae86c14d5b7d158cb9920278e46407118ac Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 26 Nov 2020 12:35:49 +0900 Subject: [PATCH 201/394] Add failing test to check non-pooled lifetime --- .../Gameplay/TestSceneDrawableScrollingRuleset.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index cebe0394c7..7425c2b7c4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -46,10 +46,11 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUp] public void Setup() => Schedule(() => testClock.CurrentTime = 0); - [Test] - public void TestHitObjectPooling() + [TestCase("pooled")] + [TestCase("non-pooled")] + public void TestHitObjectLifetime(string pooled) { - var beatmap = createBeatmap(_ => new TestPooledHitObject()); + var beatmap = createBeatmap(_ => pooled == "pooled" ? new TestPooledHitObject() : new TestHitObject()); beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); createTest(beatmap); @@ -208,13 +209,13 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// Get a corresponding to the 'th . - /// When a pooling is used and the hit object is not alive, `null` is returned. + /// When the hit object is not alive, `null` is returned. /// [CanBeNull] private DrawableTestHitObject getDrawableHitObject(int index) { var hitObject = drawableRuleset.Beatmap.HitObjects.ElementAt(index); - return (DrawableTestHitObject)drawableRuleset.Playfield.HitObjectContainer.Objects.FirstOrDefault(obj => obj.HitObject == hitObject); + return (DrawableTestHitObject)drawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault(obj => obj.HitObject == hitObject); } private float yScale => drawableRuleset.Playfield.HitObjectContainer.DrawHeight; @@ -426,6 +427,7 @@ namespace osu.Game.Tests.Visual.Gameplay } }); } + protected override void Update() => LifetimeEnd = HitObject.EndTime; } private class DrawableTestPooledHitObject : DrawableTestHitObject @@ -436,8 +438,6 @@ namespace osu.Game.Tests.Visual.Gameplay InternalChildren[0].Colour = Color4.LightSkyBlue; InternalChildren[1].Colour = Color4.Blue; } - - protected override void Update() => LifetimeEnd = HitObject.EndTime; } private class DrawableTestParentHitObject : DrawableTestHitObject From 9131546876b33e7a6cf56e18daddea2bbfca089e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 26 Nov 2020 13:04:19 +0900 Subject: [PATCH 202/394] Workaround TestSceneCatchModRelax failure --- .../Mods/TestSceneCatchModRelax.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs index 1eb0975010..c01aff0aa0 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs @@ -38,17 +38,17 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods new Fruit { X = 0, - StartTime = 250 + StartTime = 1000 }, new Fruit { X = CatchPlayfield.WIDTH, - StartTime = 500 + StartTime = 2000 }, new JuiceStream { X = CatchPlayfield.CENTER_X, - StartTime = 750, + StartTime = 3000, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }) } } From f6faf95e339960d83aa227d36b37e3b8fee1170e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 26 Nov 2020 14:01:46 +0900 Subject: [PATCH 203/394] Revert changes to HitObjectUsageBegan, not use it. --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 10 ++++---- osu.Game/Rulesets/UI/Playfield.cs | 4 ++-- .../Scrolling/ScrollingHitObjectContainer.cs | 24 +++++-------------- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index fb91108605..5fbda305c8 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.UI /// /// If this uses pooled objects, this represents the time when the s become alive. /// - internal event Action HitObjectUsageBegan; + internal event Action HitObjectUsageBegan; /// /// Invoked when a becomes unused by a . @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.UI /// /// If this uses pooled objects, this represents the time when the s become dead. /// - internal event Action HitObjectUsageFinished; + internal event Action HitObjectUsageFinished; /// /// The amount of time prior to the current time within which s should be considered alive. @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.UI bindStartTime(drawable); AddInternal(drawableMap[entry] = drawable, false); - HitObjectUsageBegan?.Invoke(drawable); + HitObjectUsageBegan?.Invoke(entry.HitObject); } private void removeDrawable(HitObjectLifetimeEntry entry) @@ -129,10 +129,10 @@ namespace osu.Game.Rulesets.UI drawableMap.Remove(entry); - HitObjectUsageFinished?.Invoke(drawable); - unbindStartTime(drawable); RemoveInternal(drawable); + + HitObjectUsageFinished?.Invoke(entry.HitObject); } #endregion diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 411bda77b8..2f589f4ce9 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -92,8 +92,8 @@ namespace osu.Game.Rulesets.UI { h.NewResult += (d, r) => NewResult?.Invoke(d, r); h.RevertResult += (d, r) => RevertResult?.Invoke(d, r); - h.HitObjectUsageBegan += o => HitObjectUsageBegan?.Invoke(o.HitObject); - h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o.HitObject); + h.HitObjectUsageBegan += o => HitObjectUsageBegan?.Invoke(o); + h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o); })); } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index c8afe76f19..44732f490b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -17,10 +17,6 @@ namespace osu.Game.Rulesets.UI.Scrolling private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); - // Tracks all `DrawableHitObject` (nested or not) applied a `HitObject`. - // It dynamically changes based on approximate lifetime when a pooling is used. - private readonly HashSet hitObjectApplied = new HashSet(); - // The lifetime of a hit object in this will be computed in next update. private readonly HashSet toComputeLifetime = new HashSet(); @@ -39,9 +35,6 @@ namespace osu.Game.Rulesets.UI.Scrolling RelativeSizeAxes = Axes.Both; AddLayout(layoutCache); - - HitObjectUsageBegan += onHitObjectUsageBegin; - HitObjectUsageFinished += onHitObjectUsageFinished; } [BackgroundDependencyLoader] @@ -58,7 +51,6 @@ namespace osu.Game.Rulesets.UI.Scrolling { base.Clear(disposeChildren); - hitObjectApplied.Clear(); toComputeLifetime.Clear(); layoutComputed.Clear(); } @@ -159,18 +151,10 @@ namespace osu.Game.Rulesets.UI.Scrolling { // Lifetime computation is delayed until next update because // when the hit object is not pooled this container is not loaded here and `scrollLength` cannot be computed. - hitObjectApplied.Add(hitObject); toComputeLifetime.Add(hitObject); layoutComputed.Remove(hitObject); } - private void onHitObjectUsageFinished(DrawableHitObject hitObject) - { - hitObjectApplied.Remove(hitObject); - toComputeLifetime.Remove(hitObject); - layoutComputed.Remove(hitObject); - } - private float scrollLength; protected override void Update() @@ -179,8 +163,12 @@ namespace osu.Game.Rulesets.UI.Scrolling if (!layoutCache.IsValid) { - foreach (var hitObject in hitObjectApplied) - toComputeLifetime.Add(hitObject); + toComputeLifetime.Clear(); + foreach (var hitObject in Objects) + { + if (hitObject.HitObject != null) + toComputeLifetime.Add(hitObject); + } layoutComputed.Clear(); From e43f9285889ab3f649418f7fe74fb40979c33772 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 26 Nov 2020 14:07:40 +0900 Subject: [PATCH 204/394] Use DHO.HitObjectApplied to invalidate computation --- .../UI/Scrolling/ScrollingHitObjectContainer.cs | 5 ++++- .../Rulesets/UI/Scrolling/ScrollingPlayfield.cs | 13 +++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 44732f490b..6740fcf03d 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -147,7 +147,10 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - private void onHitObjectUsageBegin(DrawableHitObject hitObject) + /// + /// Make this lifetime and layout computed in next update. + /// + internal void InvalidateHitObject(DrawableHitObject hitObject) { // Lifetime computation is delayed until next update because // when the hit object is not pooled this container is not loaded here and `scrollLength` cannot be computed. diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index 9dac3f4de1..8aba896b34 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.UI.Scrolling { protected readonly IBindable Direction = new Bindable(); + public new ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)base.HitObjectContainer; + [Resolved] protected IScrollingInfo ScrollingInfo { get; private set; } @@ -24,17 +26,20 @@ namespace osu.Game.Rulesets.UI.Scrolling Direction.BindTo(ScrollingInfo.Direction); } + protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject) + { + drawableHitObject.HitObjectApplied += d => HitObjectContainer.InvalidateHitObject(d); + } + /// /// Given a position in screen space, return the time within this column. /// - public virtual double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) => - ((ScrollingHitObjectContainer)HitObjectContainer).TimeAtScreenSpacePosition(screenSpacePosition); + public virtual double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) => HitObjectContainer.TimeAtScreenSpacePosition(screenSpacePosition); /// /// Given a time, return the screen space position within this column. /// - public virtual Vector2 ScreenSpacePositionAtTime(double time) - => ((ScrollingHitObjectContainer)HitObjectContainer).ScreenSpacePositionAtTime(time); + public virtual Vector2 ScreenSpacePositionAtTime(double time) => HitObjectContainer.ScreenSpacePositionAtTime(time); protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(); } From eae33fe74a56e7d8d8b8f7dce7b29172e57de8ce Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 26 Nov 2020 14:16:33 +0900 Subject: [PATCH 205/394] Fix format --- .../Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs | 1 + osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 7425c2b7c4..257ae10d82 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -427,6 +427,7 @@ namespace osu.Game.Tests.Visual.Gameplay } }); } + protected override void Update() => LifetimeEnd = HitObject.EndTime; } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 6740fcf03d..02ee39e1b8 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -167,6 +167,7 @@ namespace osu.Game.Rulesets.UI.Scrolling if (!layoutCache.IsValid) { toComputeLifetime.Clear(); + foreach (var hitObject in Objects) { if (hitObject.HitObject != null) From 8a73b335f3b59bf84e2b71c7d82ba673a4e78375 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 26 Nov 2020 14:26:38 +0900 Subject: [PATCH 206/394] Move catch piece files --- osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs | 1 + osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs | 1 + .../Objects/Drawables/{ => Pieces}/BananaPiece.cs | 3 +-- .../Objects/Drawables/{ => Pieces}/DropletPiece.cs | 3 +-- .../Objects/Drawables/{ => Pieces}/FruitPiece.cs | 2 +- .../Objects/Drawables/{ => Pieces}/GrapePiece.cs | 3 +-- .../Objects/Drawables/{ => Pieces}/PearPiece.cs | 3 +-- .../Objects/Drawables/{ => Pieces}/PineapplePiece.cs | 3 +-- .../Objects/Drawables/{ => Pieces}/PulpFormation.cs | 2 +- .../Objects/Drawables/{ => Pieces}/RaspberryPiece.cs | 3 +-- 10 files changed, 10 insertions(+), 14 deletions(-) rename osu.Game.Rulesets.Catch/Objects/Drawables/{ => Pieces}/BananaPiece.cs (88%) rename osu.Game.Rulesets.Catch/Objects/Drawables/{ => Pieces}/DropletPiece.cs (95%) rename osu.Game.Rulesets.Catch/Objects/Drawables/{ => Pieces}/FruitPiece.cs (98%) rename osu.Game.Rulesets.Catch/Objects/Drawables/{ => Pieces}/GrapePiece.cs (92%) rename osu.Game.Rulesets.Catch/Objects/Drawables/{ => Pieces}/PearPiece.cs (92%) rename osu.Game.Rulesets.Catch/Objects/Drawables/{ => Pieces}/PineapplePiece.cs (93%) rename osu.Game.Rulesets.Catch/Objects/Drawables/{ => Pieces}/PulpFormation.cs (96%) rename osu.Game.Rulesets.Catch/Objects/Drawables/{ => Pieces}/RaspberryPiece.cs (93%) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index 37a05b5d76..74cd240aa3 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Utils; +using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Objects.Drawables diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index b000a728c1..96e24bf76c 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Utils; +using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Objects.Drawables diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/BananaPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/BananaPiece.cs similarity index 88% rename from osu.Game.Rulesets.Catch/Objects/Drawables/BananaPiece.cs rename to osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/BananaPiece.cs index ebb0bf0f2c..fa8837dec5 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/BananaPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/BananaPiece.cs @@ -2,10 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osuTK; -namespace osu.Game.Rulesets.Catch.Objects.Drawables +namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces { public class BananaPiece : PulpFormation { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/DropletPiece.cs similarity index 95% rename from osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs rename to osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/DropletPiece.cs index dd0723c744..6de3167958 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/DropletPiece.cs @@ -5,12 +5,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Drawables; using osuTK; -namespace osu.Game.Rulesets.Catch.Objects.Drawables +namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces { public class DropletPiece : CompositeDrawable { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs similarity index 98% rename from osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs rename to osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs index f98050ae91..632c8e6f3a 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Drawables; using osuTK.Graphics; -namespace osu.Game.Rulesets.Catch.Objects.Drawables +namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces { internal class FruitPiece : CompositeDrawable { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/GrapePiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/GrapePiece.cs similarity index 92% rename from osu.Game.Rulesets.Catch/Objects/Drawables/GrapePiece.cs rename to osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/GrapePiece.cs index 1d1faf893b..15349c18d5 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/GrapePiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/GrapePiece.cs @@ -2,10 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osuTK; -namespace osu.Game.Rulesets.Catch.Objects.Drawables +namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces { public class GrapePiece : PulpFormation { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/PearPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/PearPiece.cs similarity index 92% rename from osu.Game.Rulesets.Catch/Objects/Drawables/PearPiece.cs rename to osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/PearPiece.cs index 7f14217cda..3372a06996 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/PearPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/PearPiece.cs @@ -2,10 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osuTK; -namespace osu.Game.Rulesets.Catch.Objects.Drawables +namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces { public class PearPiece : PulpFormation { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/PineapplePiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/PineapplePiece.cs similarity index 93% rename from osu.Game.Rulesets.Catch/Objects/Drawables/PineapplePiece.cs rename to osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/PineapplePiece.cs index c328ba1837..7f80c58178 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/PineapplePiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/PineapplePiece.cs @@ -2,10 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osuTK; -namespace osu.Game.Rulesets.Catch.Objects.Drawables +namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces { public class PineapplePiece : PulpFormation { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/PulpFormation.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/PulpFormation.cs similarity index 96% rename from osu.Game.Rulesets.Catch/Objects/Drawables/PulpFormation.cs rename to osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/PulpFormation.cs index be70c3400c..1df548e70a 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/PulpFormation.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/PulpFormation.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osuTK; using osuTK.Graphics; -namespace osu.Game.Rulesets.Catch.Objects.Drawables +namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces { public abstract class PulpFormation : CompositeDrawable { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/RaspberryPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/RaspberryPiece.cs similarity index 93% rename from osu.Game.Rulesets.Catch/Objects/Drawables/RaspberryPiece.cs rename to osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/RaspberryPiece.cs index 22ce3ba5b3..288ece95b2 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/RaspberryPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/RaspberryPiece.cs @@ -2,10 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osuTK; -namespace osu.Game.Rulesets.Catch.Objects.Drawables +namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces { public class RaspberryPiece : PulpFormation { From cafe8cf7fad552ed741afacd751f39147ee2da51 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 26 Nov 2020 14:36:42 +0900 Subject: [PATCH 207/394] Refactor border of fruits to classes --- .../Objects/Drawables/Pieces/BorderPiece.cs | 31 +++++++++++++ .../Objects/Drawables/Pieces/DropletPiece.cs | 33 +------------- .../Objects/Drawables/Pieces/FruitPiece.cs | 43 ++----------------- .../Drawables/Pieces/HyperBorderPiece.cs | 22 ++++++++++ .../Pieces/HyperDropletBorderPiece.cs | 14 ++++++ 5 files changed, 71 insertions(+), 72 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/BorderPiece.cs create mode 100644 osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/HyperBorderPiece.cs create mode 100644 osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/HyperDropletBorderPiece.cs diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/BorderPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/BorderPiece.cs new file mode 100644 index 0000000000..1e7a0b0685 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/BorderPiece.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces +{ + public class BorderPiece : Circle + { + public BorderPiece() + { + Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2); + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + BorderColour = Color4.White; + BorderThickness = 6f * FruitPiece.RADIUS_ADJUST; + + // Border is drawn only when there is a child drawable. + Child = new Box + { + AlwaysPresent = true, + Alpha = 0, + RelativeSizeAxes = Axes.Both, + }; + } + } +} + diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/DropletPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/DropletPiece.cs index 6de3167958..bcef30fda8 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/DropletPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/DropletPiece.cs @@ -4,8 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Drawables; using osuTK; @@ -31,36 +29,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces if (drawableCatchObject.HitObject.HyperDash) { - AddInternal(new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(2f), - Depth = 1, - Children = new Drawable[] - { - new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR, - BorderThickness = 6, - Children = new Drawable[] - { - new Box - { - AlwaysPresent = true, - Alpha = 0.3f, - Blending = BlendingParameters.Additive, - RelativeSizeAxes = Axes.Both, - Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR, - } - } - } - } - }); + AddInternal(new HyperDropletBorderPiece()); } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs index 632c8e6f3a..208c9f8316 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs @@ -5,10 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Drawables; -using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces { @@ -19,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces /// public const float RADIUS_ADJUST = 1.1f; - private Circle border; + private BorderPiece border; private PalpableCatchHitObject hitObject; public FruitPiece() @@ -36,46 +33,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces AddRangeInternal(new[] { getFruitFor(hitObject.VisualRepresentation), - border = new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - BorderColour = Color4.White, - BorderThickness = 6f * RADIUS_ADJUST, - Children = new Drawable[] - { - new Box - { - AlwaysPresent = true, - Alpha = 0, - RelativeSizeAxes = Axes.Both - } - } - }, + border = new BorderPiece(), }); if (hitObject.HyperDash) { - AddInternal(new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR, - BorderThickness = 12f * RADIUS_ADJUST, - Children = new Drawable[] - { - new Box - { - AlwaysPresent = true, - Alpha = 0.3f, - Blending = BlendingParameters.Additive, - RelativeSizeAxes = Axes.Both, - Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR, - } - } - }); + AddInternal(new HyperBorderPiece()); } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/HyperBorderPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/HyperBorderPiece.cs new file mode 100644 index 0000000000..60bb07e89d --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/HyperBorderPiece.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Catch.UI; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces +{ + public class HyperBorderPiece : BorderPiece + { + public HyperBorderPiece() + { + BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR; + BorderThickness = 12f * FruitPiece.RADIUS_ADJUST; + + Child.Alpha = 0.3f; + Child.Blending = BlendingParameters.Additive; + Child.Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR; + } + } +} + diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/HyperDropletBorderPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/HyperDropletBorderPiece.cs new file mode 100644 index 0000000000..1bd9fd6bb2 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/HyperDropletBorderPiece.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces +{ + public class HyperDropletBorderPiece : HyperBorderPiece + { + public HyperDropletBorderPiece() + { + Size /= 2; + BorderThickness = 6f; + } + } +} From f562854feb72d51a4b198db2b69e5ea5132c6131 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 26 Nov 2020 17:22:22 +0900 Subject: [PATCH 208/394] Fix timeline objects sometimes not receiving combo colours --- .../Timeline/TimelineHitObjectBlueprint.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 975433d407..d534fb3e13 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -55,6 +55,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private const float circle_size = 24; + [Resolved(CanBeNull = true)] + private HitObjectComposer composer { get; set; } + public TimelineHitObjectBlueprint(HitObject hitObject) : base(hitObject) { @@ -152,19 +155,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateShadows(); } - [BackgroundDependencyLoader(true)] - private void load(HitObjectComposer composer) + protected override void LoadComplete() { + base.LoadComplete(); + if (composer != null) { // best effort to get the drawable representation for grabbing colour and what not. drawableHitObject = composer.HitObjects.FirstOrDefault(d => d.HitObject == HitObject); } - } - - protected override void LoadComplete() - { - base.LoadComplete(); if (HitObject is IHasComboInformation comboInfo) { From f3f5ec766535b8ba201b34ed343bda7e5b8012b3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 26 Nov 2020 18:08:40 +0900 Subject: [PATCH 209/394] Fix `Column` not calling `base.Add` --- osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9aabcc6699..d2a9b69b60 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.UI DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)hitObject; maniaObject.CheckHittable = hitPolicy.IsHittable; - HitObjectContainer.Add(hitObject); + base.Add(hitObject); } public override bool Remove(DrawableHitObject h) From e53f849aa056ac40f5dc9d745a0ad1bc94541e87 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 26 Nov 2020 18:14:25 +0900 Subject: [PATCH 210/394] Completely separate combo colours from DHOs --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 28 ++++++- .../Objects/Drawables/DrawableBanana.cs | 25 ------ .../DrawablePalpableCatchHitObject.cs | 5 -- .../Objects/PalpableCatchHitObject.cs | 8 +- .../Objects/Drawables/DrawableHitObject.cs | 8 +- .../Objects/Types/IHasComboInformation.cs | 11 +++ .../Timeline/TimelineHitObjectBlueprint.cs | 84 ++++++++----------- .../Screens/Edit/Compose/ComposeScreen.cs | 20 ++++- 8 files changed, 103 insertions(+), 86 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index 4ecfb7b16d..d1033f7801 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -2,13 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Types; +using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects { - public class Banana : Fruit + public class Banana : Fruit, IHasComboInformation { /// /// Index of banana in current shower. @@ -26,6 +29,29 @@ namespace osu.Game.Rulesets.Catch.Objects Samples = samples; } + private Color4? colour; + + Color4 IHasComboInformation.GetComboColour(IReadOnlyList comboColours) + { + // override any external colour changes with banananana + return colour ??= getBananaColour(); + } + + private Color4 getBananaColour() + { + switch (RNG.Next(0, 3)) + { + default: + return new Color4(255, 240, 0, 255); + + case 1: + return new Color4(255, 192, 0, 255); + + case 2: + return new Color4(214, 221, 28, 255); + } + } + private class BananaHitSampleInfo : HitSampleInfo { private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" }; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs index a865984d45..7748b1c565 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs @@ -1,10 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Utils; -using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawables { @@ -15,14 +13,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { } - private Color4? colour; - - protected override Color4 GetComboColour(IReadOnlyList comboColours) - { - // override any external colour changes with banananana - return colour ??= getBananaColour(); - } - protected override void UpdateInitialTransforms() { base.UpdateInitialTransforms(); @@ -46,20 +36,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables if (Samples != null) Samples.Frequency.Value = 0.77f + ((Banana)HitObject).BananaIndex * 0.006f; } - - private Color4 getBananaColour() - { - switch (RNG.Next(0, 3)) - { - default: - return new Color4(255, 240, 0, 255); - - case 1: - return new Color4(255, 192, 0, 255); - - case 2: - return new Color4(214, 221, 28, 255); - } - } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs index 935aad914e..9339a1c420 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs @@ -1,12 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawables { @@ -40,8 +38,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { ScaleContainer.Scale = new Vector2(HitObject.Scale); } - - protected override Color4 GetComboColour(IReadOnlyList comboColours) => - comboColours[(HitObject.IndexInBeatmap + 1) % comboColours.Count]; } } diff --git a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs index 361b338b2c..995f61c386 100644 --- a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs @@ -1,13 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Types; +using osuTK.Graphics; + namespace osu.Game.Rulesets.Catch.Objects { /// /// Represents a single object that can be caught by the catcher. /// This includes normal fruits, droplets, and bananas but excludes objects that act only as a container of nested hit objects. /// - public abstract class PalpableCatchHitObject : CatchHitObject + public abstract class PalpableCatchHitObject : CatchHitObject, IHasComboInformation { /// /// Difference between the distance to the next object @@ -25,5 +29,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// The target fruit if we are to initiate a hyperdash. /// public CatchHitObject HyperDashTarget; + + Color4 IHasComboInformation.GetComboColour(IReadOnlyList comboColours) => comboColours[(IndexInBeatmap + 1) % comboColours.Count]; } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 537da24e01..571b05cc05 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -529,11 +529,10 @@ namespace osu.Game.Rulesets.Objects.Drawables private void updateComboColour() { - if (!(HitObject is IHasComboInformation)) return; + if (!(HitObject is IHasComboInformation combo)) return; - var comboColours = CurrentSkin.GetConfig>(GlobalSkinColours.ComboColours)?.Value; - - AccentColour.Value = GetComboColour(comboColours); + var comboColours = CurrentSkin.GetConfig>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty(); + AccentColour.Value = combo.GetComboColour(comboColours); } /// @@ -544,6 +543,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// This will only be called if the implements . /// /// A list of combo colours provided by the beatmap or skin. Can be null if not available. + [Obsolete("Unused. Implement IHasComboInformation and IHasComboInformation.GetComboColour() on the HitObject model instead.")] // Can be removed 20210527 protected virtual Color4 GetComboColour(IReadOnlyList comboColours) { if (!(HitObject is IHasComboInformation combo)) diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs index 211c077d4f..4f66802079 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs @@ -1,7 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Bindables; +using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Types { @@ -35,5 +38,13 @@ namespace osu.Game.Rulesets.Objects.Types /// Whether this is the last object in the current combo. /// bool LastInCombo { get; set; } + + /// + /// Retrieves the colour of the combo described by this object from a set of possible combo colours. + /// Defaults to using to decide the colour. + /// + /// A list of possible combo colours provided by the beatmap or skin. + /// The colour of the combo described by this object. + Color4 GetComboColour([NotNull] IReadOnlyList comboColours) => comboColours.Count > 0 ? comboColours[ComboIndex % comboColours.Count] : Color4.White; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index d534fb3e13..657c5834b2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -19,8 +18,8 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -28,35 +27,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineHitObjectBlueprint : SelectionBlueprint { - private readonly Circle circle; + private const float thickness = 5; + private const float shadow_radius = 5; + private const float circle_size = 24; + + public Action OnDragHandled; [UsedImplicitly] private readonly Bindable startTime; - public Action OnDragHandled; + private Bindable indexInCurrentComboBindable; + private Bindable comboIndexBindable; + private readonly Circle circle; private readonly DragBar dragBar; - private readonly List shadowComponents = new List(); - - private DrawableHitObject drawableHitObject; - - private Bindable comboColour; - private readonly Container mainComponents; - private readonly OsuSpriteText comboIndexText; - private Bindable comboIndex; - - private const float thickness = 5; - - private const float shadow_radius = 5; - - private const float circle_size = 24; - - [Resolved(CanBeNull = true)] - private HitObjectComposer composer { get; set; } + [Resolved] + private ISkinSource skin { get; set; } public TimelineHitObjectBlueprint(HitObject hitObject) : base(hitObject) @@ -159,38 +149,38 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadComplete(); - if (composer != null) - { - // best effort to get the drawable representation for grabbing colour and what not. - drawableHitObject = composer.HitObjects.FirstOrDefault(d => d.HitObject == HitObject); - } - if (HitObject is IHasComboInformation comboInfo) { - comboIndex = comboInfo.IndexInCurrentComboBindable.GetBoundCopy(); - comboIndex.BindValueChanged(combo => - { - comboIndexText.Text = (combo.NewValue + 1).ToString(); - }, true); + indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy(); + indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true); + + comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy(); + comboIndexBindable.BindValueChanged(_ => updateComboColour(), true); + + skin.SourceChanged += updateComboColour; } + } - if (drawableHitObject != null) - { - comboColour = drawableHitObject.AccentColour.GetBoundCopy(); - comboColour.BindValueChanged(colour => - { - if (HitObject is IHasDuration) - mainComponents.Colour = ColourInfo.GradientHorizontal(drawableHitObject.AccentColour.Value, Color4.White); - else - mainComponents.Colour = drawableHitObject.AccentColour.Value; + private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString(); - var col = mainComponents.Colour.TopLeft.Linear; - float brightness = col.R + col.G + col.B; + private void updateComboColour() + { + if (!(HitObject is IHasComboInformation combo)) + return; - // decide the combo index colour based on brightness? - comboIndexText.Colour = brightness > 0.5f ? Color4.Black : Color4.White; - }, true); - } + var comboColours = skin.GetConfig>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty(); + var comboColour = combo.GetComboColour(comboColours); + + if (HitObject is IHasDuration) + mainComponents.Colour = ColourInfo.GradientHorizontal(comboColour, Color4.White); + else + mainComponents.Colour = comboColour; + + var col = mainComponents.Colour.TopLeft.Linear; + float brightness = col.R + col.G + col.B; + + // decide the combo index colour based on brightness? + comboIndexText.Colour = brightness > 0.5f ? Color4.Black : Color4.White; } protected override void Update() diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 46d5eb40b4..c297a03dbf 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -43,6 +44,21 @@ namespace osu.Game.Screens.Edit.Compose if (ruleset == null || composer == null) return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer"); + return wrapSkinnableContent(composer); + } + + protected override Drawable CreateTimelineContent() + { + if (ruleset == null || composer == null) + return base.CreateTimelineContent(); + + return wrapSkinnableContent(new TimelineBlueprintContainer(composer)); + } + + private Drawable wrapSkinnableContent(Drawable content) + { + Debug.Assert(ruleset != null); + var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation @@ -51,9 +67,7 @@ namespace osu.Game.Screens.Edit.Compose // load the skinning hierarchy first. // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. - return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer)); + return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(content)); } - - protected override Drawable CreateTimelineContent() => composer == null ? base.CreateTimelineContent() : new TimelineBlueprintContainer(composer); } } From 02d5b1352b6c9971ea83a131c0d2637e167b56b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 18:25:56 +0900 Subject: [PATCH 211/394] Expose generic version of OsuScrollContainer --- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index b9122d254d..aaad72f65c 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -12,7 +12,19 @@ using osuTK.Input; namespace osu.Game.Graphics.Containers { - public class OsuScrollContainer : ScrollContainer + public class OsuScrollContainer : OsuScrollContainer + { + public OsuScrollContainer() + { + } + + public OsuScrollContainer(Direction direction) + : base(direction) + { + } + } + + public class OsuScrollContainer : ScrollContainer where T : Drawable { public const float SCROLL_BAR_HEIGHT = 10; public const float SCROLL_BAR_PADDING = 3; From f8db7a990283b813278f69a53d3de5863db29eb6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 18:28:52 +0900 Subject: [PATCH 212/394] Remove ScrollableContent container from carousel This was causing multiple issues with masking and sizing and really didn't need to exist in the first place. Also not sure why the pool was nested inside the scroll container, but it isn't any more. Probably for the best. --- .../SongSelect/TestSceneBeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 48 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 4699784327..44c9361ff8 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -917,7 +917,7 @@ namespace osu.Game.Tests.Visual.SongSelect { get { - foreach (var item in ScrollableContent) + foreach (var item in Scroll.Children) { yield return item; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 83631fd383..164802fc28 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Select /// public bool BeatmapSetsLoaded { get; private set; } - private readonly CarouselScrollContainer scroll; + protected readonly CarouselScrollContainer Scroll; private IEnumerable beatmapSets => root.Children.OfType(); @@ -112,7 +112,7 @@ namespace osu.Game.Screens.Select if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; - ScrollableContent.Clear(false); + Scroll.Clear(false); itemsCache.Invalidate(); scrollPositionCache.Invalidate(); @@ -132,8 +132,6 @@ namespace osu.Game.Screens.Select private readonly Cached itemsCache = new Cached(); private readonly Cached scrollPositionCache = new Cached(); - protected readonly Container ScrollableContent; - public Bindable RightClickScrollingEnabled = new Bindable(); public Bindable RandomAlgorithm = new Bindable(); @@ -155,17 +153,12 @@ namespace osu.Game.Screens.Select InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - Child = scroll = new CarouselScrollContainer + Children = new Drawable[] { - Masking = false, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + setPool, + Scroll = new CarouselScrollContainer { - setPool, - ScrollableContent = new Container - { - RelativeSizeAxes = Axes.X, - } + RelativeSizeAxes = Axes.Both, } } }; @@ -180,7 +173,7 @@ namespace osu.Game.Screens.Select config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); - RightClickScrollingEnabled.ValueChanged += enabled => scroll.RightMouseScrollbar = enabled.NewValue; + RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); itemUpdated = beatmaps.ItemUpdated.GetBoundCopy(); @@ -421,12 +414,12 @@ namespace osu.Game.Screens.Select /// /// The position of the lower visible bound with respect to the current scroll position. /// - private float visibleBottomBound => scroll.Current + DrawHeight + BleedBottom; + private float visibleBottomBound => Scroll.Current + DrawHeight + BleedBottom; /// /// The position of the upper visible bound with respect to the current scroll position. /// - private float visibleUpperBound => scroll.Current - BleedTop; + private float visibleUpperBound => Scroll.Current - BleedTop; public void FlushPendingFilterOperations() { @@ -468,7 +461,7 @@ namespace osu.Game.Screens.Select root.Filter(activeCriteria); itemsCache.Invalidate(); - if (alwaysResetScrollPosition || !scroll.UserScrolling) + if (alwaysResetScrollPosition || !Scroll.UserScrolling) ScrollToSelected(); } } @@ -594,7 +587,7 @@ namespace osu.Game.Screens.Select { var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first + 1); - foreach (var panel in ScrollableContent.Children) + foreach (var panel in Scroll.Children) { if (toDisplay.Remove(panel.Item)) { @@ -620,7 +613,7 @@ namespace osu.Game.Screens.Select panel.Depth = item.CarouselYPosition; panel.Y = item.CarouselYPosition; - ScrollableContent.Add(panel); + Scroll.Add(panel); } } } @@ -637,7 +630,7 @@ namespace osu.Game.Screens.Select // Update externally controlled state of currently visible items (e.g. x-offset and opacity). // This is a per-frame update on all drawable panels. - foreach (DrawableCarouselItem item in ScrollableContent.Children) + foreach (DrawableCarouselItem item in Scroll.Children) { updateItem(item); @@ -789,7 +782,8 @@ namespace osu.Game.Screens.Select } currentY += visibleHalfHeight; - ScrollableContent.Height = currentY; + + Scroll.ScrollContent.Height = currentY; if (BeatmapSetsLoaded && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected)) { @@ -809,7 +803,7 @@ namespace osu.Game.Screens.Select if (firstScroll) { // reduce movement when first displaying the carousel. - scroll.ScrollTo(scrollTarget.Value - 200, false); + Scroll.ScrollTo(scrollTarget.Value - 200, false); firstScroll = false; } @@ -844,7 +838,7 @@ namespace osu.Game.Screens.Select /// For nested items, the parent of the item to be updated. private void updateItem(DrawableCarouselItem item, DrawableCarouselItem parent = null) { - Vector2 posInScroll = ScrollableContent.ToLocalSpace(item.Header.ScreenSpaceDrawQuad.Centre); + Vector2 posInScroll = Scroll.ScrollContent.ToLocalSpace(item.Header.ScreenSpaceDrawQuad.Centre); float itemDrawY = posInScroll.Y - visibleUpperBound; float dist = Math.Abs(1f - itemDrawY / visibleHalfHeight); @@ -889,7 +883,7 @@ namespace osu.Game.Screens.Select } } - private class CarouselScrollContainer : OsuScrollContainer + protected class CarouselScrollContainer : OsuScrollContainer { private bool rightMouseScrollBlocked; @@ -898,6 +892,12 @@ namespace osu.Game.Screens.Select /// public bool UserScrolling { get; private set; } + public CarouselScrollContainer() + { + // size is determined by the carousel itself, due to not all content necessarily being loaded. + ScrollContent.AutoSizeAxes = Axes.None; + } + // ReSharper disable once OptionalParameterHierarchyMismatch 2020.3 EAP4 bug. (https://youtrack.jetbrains.com/issue/RSRP-481535?p=RIDER-51910) protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) { From 6058c66edb0a0e2ce4150bf8fc530f36b708e9ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 18:32:43 +0900 Subject: [PATCH 213/394] Move drawable carousel set movement logic into panels themselves --- osu.Game/Screens/Select/BeatmapCarousel.cs | 10 ---------- .../Carousel/DrawableCarouselBeatmapSet.cs | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 164802fc28..b6ed0468dd 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -618,16 +618,6 @@ namespace osu.Game.Screens.Select } } - // Finally, if the filtered items have changed, animate drawables to their new locations. - // This is common if a selected/collapsed state has changed. - if (revalidateItems) - { - foreach (DrawableCarouselItem panel in ScrollableContent.Children) - { - panel.MoveToY(panel.Item.CarouselYPosition, 800, Easing.OutQuint); - } - } - // Update externally controlled state of currently visible items (e.g. x-offset and opacity). // This is a per-frame update on all drawable panels. foreach (DrawableCarouselItem item in Scroll.Children) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 93f95e76cc..e25c6932cf 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Graphics.UserInterface; @@ -60,6 +61,25 @@ namespace osu.Game.Screens.Select.Carousel viewDetails = beatmapOverlay.FetchAndShowBeatmapSet; } + protected override void Update() + { + base.Update(); + + // position updates should not occur if the item is filtered away. + // this avoids panels flying across the screen only to be eventually off-screen or faded out. + if (!Item.Visible) + return; + + float targetY = Item.CarouselYPosition; + + if (Precision.AlmostEquals(targetY, Y)) + Y = targetY; + else + // algorithm for this is taken from ScrollContainer. + // while it doesn't necessarily need to match 1:1, as we are emulating scroll in some cases this feels most correct. + Y = (float)Interpolation.Lerp(targetY, Y, Math.Exp(-0.01 * Time.Elapsed)); + } + protected override void UpdateItem() { base.UpdateItem(); From ad258e2e52ac387dd50b35f93022fd62217e6f30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 18:33:37 +0900 Subject: [PATCH 214/394] Update scroll position before applying any panel animations In the case of automatic scroll requirements (ie. scroll to selected) we are delegating the animation logic to the panels themselves. In order to make this work correctly, the scroll operation needs to take effect before any animation updates are run. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b6ed0468dd..3eddba0532 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -573,6 +573,9 @@ namespace osu.Game.Screens.Select if (revalidateItems) updateYPositions(); + if (!scrollPositionCache.IsValid) + updateScrollPosition(); + // This data is consumed to find the currently displayable range. // This is the range we want to keep drawables for, and should exceed the visible range slightly to avoid drawable churn. var newDisplayRange = getDisplayRange(); @@ -653,14 +656,6 @@ namespace osu.Game.Screens.Select return (firstIndex, lastIndex); } - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - if (!scrollPositionCache.IsValid) - updateScrollPosition(); - } - private void beatmapRemoved(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) From 0a48dd8f764bb56cba9d53c907fc83a174f7e1b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 18:42:51 +0900 Subject: [PATCH 215/394] Delegate scroll animation to panels themselves --- osu.Game/Screens/Select/BeatmapCarousel.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3eddba0532..2012d47fd3 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -573,6 +573,9 @@ namespace osu.Game.Screens.Select if (revalidateItems) updateYPositions(); + // if there is a pending scroll action we apply it without animation and transfer the difference in position to the panels. + // due to this, scroll application needs to be run immediately after y position updates. + // if this isn't the case, the on-screen pooling / display logic following will fail briefly. if (!scrollPositionCache.IsValid) updateScrollPosition(); @@ -792,8 +795,17 @@ namespace osu.Game.Screens.Select firstScroll = false; } - scroll.ScrollTo(scrollTarget.Value); + // in order to simplify animation logic, rather than using the animated version of ScrollTo, + // we take the difference in scroll height and apply to all visible panels. + // this avoids edge cases like when the visible panels is reduced suddenly, causing ScrollContainer + // to enter clamp-special-case mode where it animates completely differently to normal. + float scrollChange = scrollTarget.Value - Scroll.Current; + + Scroll.ScrollTo(scrollTarget.Value, false); scrollPositionCache.Validate(); + + foreach (var i in Scroll.Children) + i.Y += scrollChange; } } From 05e245d4454987b27fdca0200b798eb131c73f26 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 26 Nov 2020 19:07:09 +0900 Subject: [PATCH 216/394] Allow non-pooled DHO to be reused --- osu.Game/Rulesets/UI/Playfield.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 2f589f4ce9..a2ac234471 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -135,10 +135,8 @@ namespace osu.Game.Rulesets.UI /// The DrawableHitObject to add. public virtual void Add(DrawableHitObject h) { - if (h.IsInitialized) - throw new InvalidOperationException($"{nameof(Add)} doesn't support {nameof(DrawableHitObject)} reuse. Use pooling instead."); - - onNewDrawableHitObject(h); + if (!h.IsInitialized) + onNewDrawableHitObject(h); HitObjectContainer.Add(h); OnHitObjectAdded(h.HitObject); From 9811c46e3573e8299fed6a326422704fd3aa3e53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 19:16:18 +0900 Subject: [PATCH 217/394] Rename application method to better describe what it actually does --- .../Edit/Blueprints/HoldNotePlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/NotePlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/HitPlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/TaikoSpanPlacementBlueprint.cs | 4 ++-- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 +- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 +- osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs | 4 ++-- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index b5ec1e1a2a..1f92929392 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -78,9 +78,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private double originalStartTime; - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); if (PlacementActive) { diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 27a279e044..5e09054667 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -48,9 +48,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return true; } - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); if (!PlacementActive) Column = result.Playfield as Column; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs index 684004b558..3db89c8ae6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -22,9 +22,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints InternalChild = piece = new EditNotePiece { Origin = Anchor.Centre }; } - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); if (result.Playfield != null) { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index e14d6647d2..c45a04053f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles return base.OnMouseDown(e); } - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 4b99cc23ed..b71e1914f7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -67,9 +67,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders inputManager = GetContainingInputManager(); } - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); switch (state) { diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs index c5191ab241..17e7fb81f6 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs @@ -43,10 +43,10 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints return false; } - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { piece.Position = ToLocalSpace(result.ScreenSpacePosition); - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); } } } diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index 468d980b23..e53b331f46 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints EndPlacement(true); } - public override void UpdatePosition(SnapResult result) + public override void UpdateTimeAndPosition(SnapResult result) { - base.UpdatePosition(result); + base.UpdateTimeAndPosition(result); if (PlacementActive) { diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index d986b71380..9d7dc7b8f7 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Edit /// Updates the position of this to a new screen-space position. /// /// The snap result information. - public virtual void UpdatePosition(SnapResult result) + public virtual void UpdateTimeAndPosition(SnapResult result) { if (!PlacementActive) HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current; diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 0d2e2360b1..dddfd763d5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -157,7 +157,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { var snapResult = Composer.SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position); - currentPlacement.UpdatePosition(snapResult); + currentPlacement.UpdateTimeAndPosition(snapResult); } #endregion diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index c3d74f21aa..78a6bcc3db 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual { base.Update(); - currentBlueprint.UpdatePosition(SnapForBlueprint(currentBlueprint)); + currentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(currentBlueprint)); } protected virtual SnapResult SnapForBlueprint(PlacementBlueprint blueprint) => @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual if (drawable is PlacementBlueprint blueprint) { blueprint.Show(); - blueprint.UpdatePosition(SnapForBlueprint(blueprint)); + blueprint.UpdateTimeAndPosition(SnapForBlueprint(blueprint)); } } From 91592cf32d387d332610a03f46175105ac78ccd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 19:20:39 +0900 Subject: [PATCH 218/394] Expose EditorClock for consumption --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index def5f396f1..5c5f203667 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private IEditorChangeHandler changeHandler { get; set; } [Resolved] - private EditorClock editorClock { get; set; } + protected EditorClock EditorClock { get; private set; } [Resolved] protected EditorBeatmap Beatmap { get; private set; } @@ -170,7 +170,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (clickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != clickedBlueprint) return false; - editorClock?.SeekTo(clickedBlueprint.HitObject.StartTime); + EditorClock?.SeekTo(clickedBlueprint.HitObject.StartTime); return true; } @@ -381,7 +381,7 @@ namespace osu.Game.Screens.Edit.Compose.Components case SelectionState.Selected: // if the editor is playing, we generally don't want to deselect objects even if outside the selection area. - if (!editorClock.IsRunning && !isValidForSelection()) + if (!EditorClock.IsRunning && !isValidForSelection()) blueprint.Deselect(); break; } From da6bccc8125725a06bc7d82fb1d34ae0886e0456 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 19:20:51 +0900 Subject: [PATCH 219/394] Apply beat snap if positional snap doesn't give a time result --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index dddfd763d5..1893366d90 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -157,6 +157,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { var snapResult = Composer.SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position); + // if no time was found from positional snapping, we should still quantize to the beat. + snapResult.Time ??= Beatmap.SnapTime(EditorClock.CurrentTime, null); + currentPlacement.UpdateTimeAndPosition(snapResult); } From ab1ad99c88f556afb8c1daeb2ffa849c2597e451 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 19:33:51 +0900 Subject: [PATCH 220/394] Fix failing test scene (was previously not snapped properly) --- .../Editor/TestSceneObjectObjectSnap.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index 6b532e5014..7bdf131e0d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { base.SetUpSteps(); AddStep("get playfield", () => playfield = Editor.ChildrenOfType().First()); + AddStep("seek to first control point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time)); } [TestCase(true)] @@ -66,13 +67,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("start slider placement", () => InputManager.Click(MouseButton.Left)); - AddStep("move to place end", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.185f, 0))); + AddStep("move to place end", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.225f, 0))); AddStep("end slider placement", () => InputManager.Click(MouseButton.Right)); AddStep("enter circle placement mode", () => InputManager.Key(Key.Number2)); - AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.20f, 0))); + AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.235f, 0))); AddStep("place second object", () => InputManager.Click(MouseButton.Left)); From 9a08cc8c04d8a45747736bcea22db930e53e3096 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 19:40:10 +0900 Subject: [PATCH 221/394] Add test coverage of beat snapping hit circles --- .../Editor/TestSceneBeatSnap.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneBeatSnap.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneBeatSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneBeatSnap.cs new file mode 100644 index 0000000000..a652fb32f4 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneBeatSnap.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Tests.Beatmaps; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + [TestFixture] + public class TestSceneObjectBeatSnap : TestSceneOsuEditor + { + private OsuPlayfield playfield; + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false); + + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("get playfield", () => playfield = Editor.ChildrenOfType().First()); + } + + [Test] + public void TestBeatSnapHitCircle() + { + double firstTimingPointTime() => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time; + + AddStep("seek some milliseconds forward", () => EditorClock.Seek(firstTimingPointTime() + 10)); + + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre)); + AddStep("enter placement mode", () => InputManager.Key(Key.Number2)); + AddStep("place first object", () => InputManager.Click(MouseButton.Left)); + + AddAssert("ensure object snapped back to correct time", () => EditorBeatmap.HitObjects.First().StartTime == firstTimingPointTime()); + } + } +} From 203c36f7202f4fb869c548fc1d71b5d89d4e4fbc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 19:46:54 +0900 Subject: [PATCH 222/394] Rename file to match test name --- .../Editor/{TestSceneBeatSnap.cs => TestSceneObjectBeatSnap.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Rulesets.Osu.Tests/Editor/{TestSceneBeatSnap.cs => TestSceneObjectBeatSnap.cs} (100%) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneBeatSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectBeatSnap.cs similarity index 100% rename from osu.Game.Rulesets.Osu.Tests/Editor/TestSceneBeatSnap.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectBeatSnap.cs From 3346c06aca2469288f1aa2a4ed48fc7595c97122 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 20:04:44 +0900 Subject: [PATCH 223/394] Rename variable/text to be more verbose as to toggle purpose --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 +++--- osu.Game/Screens/Play/ReplayPlayer.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 77f57bd637..2736c20da9 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -69,7 +69,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), - new KeyBinding(InputKey.Space, GlobalAction.PauseReplay), + new KeyBinding(InputKey.Space, GlobalAction.TogglePauseReplay), new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), }; @@ -198,7 +198,7 @@ namespace osu.Game.Input.Bindings [Description("Random Skin")] RandomSkin, - [Description("Pause Replay")] - PauseReplay, + [Description("Pause / resume replay")] + TogglePauseReplay, } } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 2c0b766a17..294d116f51 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Play { switch (action) { - case GlobalAction.PauseReplay: + case GlobalAction.TogglePauseReplay: if (GameplayClockContainer.IsPaused.Value) GameplayClockContainer.Start(); else From 1e79cb498b8454d2a6349bae36d7ff0fe788f484 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 20:07:16 +0900 Subject: [PATCH 224/394] Standardise binding description case to sentence casing --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index e5d3a89a88..9af1b49ef9 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -163,10 +163,10 @@ namespace osu.Game.Input.Bindings [Description("Toggle now playing overlay")] ToggleNowPlaying, - [Description("Previous Selection")] + [Description("Previous selection")] SelectPrevious, - [Description("Next Selection")] + [Description("Next selection")] SelectNext, [Description("Home")] @@ -175,26 +175,26 @@ namespace osu.Game.Input.Bindings [Description("Toggle notifications")] ToggleNotifications, - [Description("Pause")] + [Description("Pause gameplay")] PauseGameplay, // Editor - [Description("Setup Mode")] + [Description("Setup mode")] EditorSetupMode, - [Description("Compose Mode")] + [Description("Compose mode")] EditorComposeMode, - [Description("Design Mode")] + [Description("Design mode")] EditorDesignMode, - [Description("Timing Mode")] + [Description("Timing mode")] EditorTimingMode, [Description("Hold for HUD")] HoldForHUD, - [Description("Random Skin")] + [Description("Random skin")] RandomSkin, } } From aa4da2a5f82af9141e1efeee7a547313822165c0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 26 Nov 2020 23:42:05 +0900 Subject: [PATCH 225/394] Add xmldoc on State --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 4c55938e49..1427453c5f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -128,6 +128,12 @@ namespace osu.Game.Rulesets.Objects.Drawables private readonly Bindable state = new Bindable(); + /// + /// The state of this . + /// + /// + /// For pooled hitobjects, is recommended to be used instead for better editor/rewinding support. + /// public IBindable State => state; /// From 57454bbb1c50d0af8bf9e1d2a9da5db9993e354f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Nov 2020 10:13:05 +0900 Subject: [PATCH 226/394] Remove hitObject argument from OnApply and OnFree --- .../Objects/Drawables/DrawableOsuHitObject.cs | 8 ++++---- .../Objects/Drawables/DrawableSlider.cs | 8 ++++---- .../Objects/Drawables/DrawableSliderHead.cs | 4 ++-- .../Visual/Gameplay/TestScenePoolingRuleset.cs | 4 ++-- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 10 ++++------ 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index a26db06ede..4b7f048c1b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -53,9 +53,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }); } - protected override void OnApply(HitObject hitObject) + protected override void OnApply() { - base.OnApply(hitObject); + base.OnApply(); IndexInCurrentComboBindable.BindTo(HitObject.IndexInCurrentComboBindable); PositionBindable.BindTo(HitObject.PositionBindable); @@ -70,9 +70,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss) + 1000; } - protected override void OnFree(HitObject hitObject) + protected override void OnFree() { - base.OnFree(hitObject); + base.OnFree(); IndexInCurrentComboBindable.UnbindFrom(HitObject.IndexInCurrentComboBindable); PositionBindable.UnbindFrom(HitObject.PositionBindable); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 6340367593..dd27ac990e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -86,18 +86,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Tracking.BindValueChanged(updateSlidingSample); } - protected override void OnApply(HitObject hitObject) + protected override void OnApply() { - base.OnApply(hitObject); + base.OnApply(); // Ensure that the version will change after the upcoming BindTo(). pathVersion.Value = int.MaxValue; PathVersion.BindTo(HitObject.Path.Version); } - protected override void OnFree(HitObject hitObject) + protected override void OnFree() { - base.OnFree(hitObject); + base.OnFree(); PathVersion.UnbindFrom(HitObject.Path.Version); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index fd0f35d20d..b5c33c0924 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -36,9 +36,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables pathVersion.BindValueChanged(_ => updatePosition()); } - protected override void OnFree(HitObject hitObject) + protected override void OnFree() { - base.OnFree(hitObject); + base.OnFree(); pathVersion.UnbindFrom(drawableSlider.PathVersion); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index 3e777119c4..cd7d692b0a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -261,9 +261,9 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - protected override void OnApply(HitObject hitObject) + protected override void OnApply() { - base.OnApply(hitObject); + base.OnApply(); Position = new Vector2(RNG.Next(-200, 200), RNG.Next(-200, 200)); } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 2e37e8977a..9c799bcf32 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -260,7 +260,7 @@ namespace osu.Game.Rulesets.Objects.Drawables HitObject.DefaultsApplied += onDefaultsApplied; - OnApply(hitObject); + OnApply(); HitObjectApplied?.Invoke(this); // If not loaded, the state update happens in LoadComplete(). Otherwise, the update is scheduled to allow for lifetime updates. @@ -315,7 +315,7 @@ namespace osu.Game.Rulesets.Objects.Drawables HitObject.DefaultsApplied -= onDefaultsApplied; - OnFree(HitObject); + OnFree(); HitObject = null; Result = null; @@ -340,16 +340,14 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Invoked for this to take on any values from a newly-applied . /// - /// The being applied. - protected virtual void OnApply(HitObject hitObject) + protected virtual void OnApply() { } /// /// Invoked for this to revert any values previously taken on from the currently-applied . /// - /// The currently-applied . - protected virtual void OnFree(HitObject hitObject) + protected virtual void OnFree() { } From fe85b7d482a0aabb076387f174fbbc9ab7835e77 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Nov 2020 10:18:00 +0900 Subject: [PATCH 227/394] Remove unused import --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index b5c33c0924..3a92938d75 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -4,7 +4,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; From c272fda41628b34e5a3d4ec7f4eb80ddf7ee2fa7 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Nov 2020 10:31:18 +0900 Subject: [PATCH 228/394] Add bindables to catch hit objects --- .../Objects/CatchHitObject.cs | 45 ++++++++++++++++--- .../Objects/PalpableCatchHitObject.cs | 17 ++++++- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index ccd2422381..05b2a21794 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -16,25 +16,47 @@ namespace osu.Game.Rulesets.Catch.Objects { public const float OBJECT_RADIUS = 64; - private float x; + // This value is after XOffset applied. + public readonly Bindable XBindable = new Bindable(); + + // This value is before XOffset applied. + private float originalX; /// /// The horizontal position of the fruit between 0 and . /// public float X { - get => x + XOffset; - set => x = value; + // TODO: I don't like this asymmetry. + get => XBindable.Value; + // originalX is set by `XBindable.BindValueChanged` + set => XBindable.Value = value + xOffset; } + private float xOffset; + /// /// A random offset applied to , set by the . /// - internal float XOffset { get; set; } + internal float XOffset + { + get => xOffset; + set + { + xOffset = value; + XBindable.Value = originalX + xOffset; + } + } public double TimePreempt = 1000; - public int IndexInBeatmap { get; set; } + public readonly Bindable IndexInBeatmapBindable = new Bindable(); + + public int IndexInBeatmap + { + get => IndexInBeatmapBindable.Value; + set => IndexInBeatmapBindable.Value = value; + } public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4); @@ -69,7 +91,13 @@ namespace osu.Game.Rulesets.Catch.Objects set => LastInComboBindable.Value = value; } - public float Scale { get; set; } = 1; + public readonly Bindable ScaleBindable = new Bindable(1); + + public float Scale + { + get => ScaleBindable.Value; + set => ScaleBindable.Value = value; + } protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { @@ -81,6 +109,11 @@ namespace osu.Game.Rulesets.Catch.Objects } protected override HitWindows CreateHitWindows() => HitWindows.Empty; + + protected CatchHitObject() + { + XBindable.BindValueChanged(x => originalX = x.NewValue - xOffset); + } } public enum FruitVisualRepresentation diff --git a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs index 995f61c386..0cd3af01df 100644 --- a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Types; using osuTK.Graphics; @@ -20,15 +21,27 @@ namespace osu.Game.Rulesets.Catch.Objects /// public float DistanceToHyperDash { get; set; } + public readonly Bindable HyperDashBindable = new Bindable(); + /// /// Whether this fruit can initiate a hyperdash. /// - public bool HyperDash => HyperDashTarget != null; + public bool HyperDash => HyperDashBindable.Value; + + private CatchHitObject hyperDashTarget; /// /// The target fruit if we are to initiate a hyperdash. /// - public CatchHitObject HyperDashTarget; + public CatchHitObject HyperDashTarget + { + get => hyperDashTarget; + set + { + hyperDashTarget = value; + HyperDashBindable.Value = value != null; + } + } Color4 IHasComboInformation.GetComboColour(IReadOnlyList comboColours) => comboColours[(IndexInBeatmap + 1) % comboColours.Count]; } From 5e36fb322af95b47e31530e961c9a6fe8879528b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Nov 2020 10:10:05 +0900 Subject: [PATCH 229/394] Move fruit visual logic from CHO to DrawableFruit --- .../TestSceneFruitObjects.cs | 17 +++--- osu.Game.Rulesets.Catch/Objects/Banana.cs | 2 - .../Objects/BananaShower.cs | 2 - .../Objects/CatchHitObject.cs | 11 ---- .../Objects/Drawables/DrawableBanana.cs | 2 + .../Objects/Drawables/DrawableFruit.cs | 59 ++++++++++++++++++- .../Objects/Drawables/Pieces/FruitPiece.cs | 26 +++----- 7 files changed, 77 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index e8ecd2ca1b..93ffb947e1 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Tests } private Drawable createDrawableFruit(FruitVisualRepresentation rep, bool hyperdash = false) => - setProperties(new DrawableFruit(new TestCatchFruit(rep)), hyperdash); + setProperties(new TestDrawableFruit(new Fruit(), rep), hyperdash); private Drawable createDrawableDroplet(bool hyperdash = false) => setProperties(new DrawableDroplet(new Droplet()), hyperdash); @@ -56,14 +56,17 @@ namespace osu.Game.Rulesets.Catch.Tests return d; } - public class TestCatchFruit : Fruit + public class TestDrawableFruit : DrawableFruit { - public TestCatchFruit(FruitVisualRepresentation rep) - { - VisualRepresentation = rep; - } + private readonly FruitVisualRepresentation visualRepresentation; - public override FruitVisualRepresentation VisualRepresentation { get; } + protected override FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => visualRepresentation; + + public TestDrawableFruit(Fruit fruit, FruitVisualRepresentation rep) + : base(fruit) + { + visualRepresentation = rep; + } } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index d1033f7801..ccb1fff15b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Catch.Objects /// public int BananaIndex; - public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; - public override Judgement CreateJudgement() => new CatchBananaJudgement(); private static readonly List samples = new List { new BananaHitSampleInfo() }; diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 89c51459a6..b45f95a8e6 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -9,8 +9,6 @@ namespace osu.Game.Rulesets.Catch.Objects { public class BananaShower : CatchHitObject, IHasDuration { - public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; - public override bool LastInCombo => true; public override Judgement CreateJudgement() => new IgnoreJudgement(); diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 05b2a21794..a74055bff9 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -58,8 +58,6 @@ namespace osu.Game.Rulesets.Catch.Objects set => IndexInBeatmapBindable.Value = value; } - public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4); - public virtual bool NewCombo { get; set; } public int ComboOffset { get; set; } @@ -115,13 +113,4 @@ namespace osu.Game.Rulesets.Catch.Objects XBindable.BindValueChanged(x => originalX = x.NewValue - xOffset); } } - - public enum FruitVisualRepresentation - { - Pear, - Grape, - Pineapple, - Raspberry, - Banana // banananananannaanana - } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs index 7748b1c565..efb0958a3a 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs @@ -8,6 +8,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { public class DrawableBanana : DrawableFruit { + protected override FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => FruitVisualRepresentation.Banana; + public DrawableBanana(Banana h) : base(h) { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index 96e24bf76c..f4adabdbcb 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osu.Game.Skinning; @@ -11,18 +12,61 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { public class DrawableFruit : DrawablePalpableCatchHitObject { + public readonly Bindable IndexInBeatmap = new Bindable(); + + public readonly Bindable VisualRepresentation = new Bindable(); + + protected virtual FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => (FruitVisualRepresentation)(indexInBeatmap % 4); + + private FruitPiece fruitPiece; + public DrawableFruit(CatchHitObject h) : base(h) { + IndexInBeatmap.Value = h.IndexInBeatmap; } [BackgroundDependencyLoader] private void load() { - ScaleContainer.Child = new SkinnableDrawable( - new CatchSkinComponent(getComponent(HitObject.VisualRepresentation)), _ => new FruitPiece()); - ScaleContainer.Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; + + IndexInBeatmap.BindValueChanged(change => + { + VisualRepresentation.Value = GetVisualRepresentation(change.NewValue); + }, true); + + VisualRepresentation.BindValueChanged(change => + { + ScaleContainer.Child = new SkinnableDrawable( + new CatchSkinComponent(getComponent(change.NewValue)), + _ => fruitPiece = new FruitPiece + { + VisualRepresentation = { BindTarget = VisualRepresentation }, + }); + }, true); + } + + protected override void OnApply() + { + base.OnApply(); + + IndexInBeatmap.BindTo(HitObject.IndexInBeatmapBindable); + } + + protected override void OnFree() + { + IndexInBeatmap.UnbindFrom(HitObject.IndexInBeatmapBindable); + + base.OnFree(); + } + + protected override void Update() + { + base.Update(); + + if (fruitPiece != null) + fruitPiece.Border.Alpha = (float)Math.Clamp((StartTimeBindable.Value - Time.Current) / 500, 0, 1); } private CatchSkinComponents getComponent(FruitVisualRepresentation hitObjectVisualRepresentation) @@ -49,4 +93,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables } } } + + public enum FruitVisualRepresentation + { + Pear, + Grape, + Pineapple, + Raspberry, + Banana // banananananannaanana + } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs index 208c9f8316..a9a8f551ce 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs @@ -1,11 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces { @@ -16,8 +15,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces /// public const float RADIUS_ADJUST = 1.1f; - private BorderPiece border; - private PalpableCatchHitObject hitObject; + public readonly Bindable VisualRepresentation = new Bindable(); + public readonly Bindable HyperDash = new Bindable(); + + public BorderPiece Border; public FruitPiece() { @@ -25,29 +26,20 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces } [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableObject) + private void load() { - var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject; - hitObject = drawableCatchObject.HitObject; - AddRangeInternal(new[] { - getFruitFor(hitObject.VisualRepresentation), - border = new BorderPiece(), + getFruitFor(VisualRepresentation.Value), + Border = new BorderPiece(), }); - if (hitObject.HyperDash) + if (HyperDash.Value) { AddInternal(new HyperBorderPiece()); } } - protected override void Update() - { - base.Update(); - border.Alpha = (float)Math.Clamp((hitObject.StartTime - Time.Current) / 500, 0, 1); - } - private Drawable getFruitFor(FruitVisualRepresentation representation) { switch (representation) From 23109f5bbced8d8725ff9fd9979e0178f3ee15cb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Nov 2020 10:55:33 +0900 Subject: [PATCH 230/394] Add bindable to drawable catch hit obejcts --- .../Drawables/DrawableCatchHitObject.cs | 21 ++++++++++- .../Objects/Drawables/DrawableFruit.cs | 22 +++++++----- .../DrawablePalpableCatchHitObject.cs | 36 ++++++++++++++++++- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index f9f534f9ab..92b4f7cae2 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Drawables; @@ -10,6 +11,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { public abstract class DrawableCatchHitObject : DrawableHitObject { + public readonly Bindable XBindable = new Bindable(); + protected override double InitialLifetimeOffset => HitObject.TimePreempt; public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale; @@ -19,10 +22,26 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables protected DrawableCatchHitObject(CatchHitObject hitObject) : base(hitObject) { - X = hitObject.X; + if (hitObject != null) + XBindable.Value = hitObject.X; + Anchor = Anchor.BottomLeft; } + protected override void OnApply() + { + base.OnApply(); + + XBindable.BindTo(HitObject.XBindable); + } + + protected override void OnFree() + { + base.OnFree(); + + XBindable.UnbindFrom(HitObject.XBindable); + } + public Func CheckPosition; public bool IsOnPlate; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index f4adabdbcb..d53bcd3a6f 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -36,15 +36,19 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables VisualRepresentation.Value = GetVisualRepresentation(change.NewValue); }, true); - VisualRepresentation.BindValueChanged(change => - { - ScaleContainer.Child = new SkinnableDrawable( - new CatchSkinComponent(getComponent(change.NewValue)), - _ => fruitPiece = new FruitPiece - { - VisualRepresentation = { BindTarget = VisualRepresentation }, - }); - }, true); + VisualRepresentation.BindValueChanged(_ => updatePiece()); + HyperDash.BindValueChanged(_ => updatePiece(), true); + } + + private void updatePiece() + { + ScaleContainer.Child = new SkinnableDrawable( + new CatchSkinComponent(getComponent(VisualRepresentation.Value)), + _ => fruitPiece = new FruitPiece + { + VisualRepresentation = { BindTarget = VisualRepresentation }, + HyperDash = { BindTarget = HyperDash }, + }); } protected override void OnApply() diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs index 9339a1c420..95f90a3eee 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osuTK; @@ -12,6 +13,15 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject; + public Bindable HyperDash { get; } = new Bindable(); + + public Bindable ScaleBindable { get; } = new Bindable(1); + + /// + /// The multiplicative factor applied to scale relative to scale. + /// + protected virtual float ScaleFactor => 1; + /// /// Whether this hit object should stay on the catcher plate when the object is caught by the catcher. /// @@ -36,7 +46,31 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables [BackgroundDependencyLoader] private void load() { - ScaleContainer.Scale = new Vector2(HitObject.Scale); + XBindable.BindValueChanged(x => + { + if (!IsOnPlate) X = x.NewValue; + }, true); + + ScaleBindable.BindValueChanged(scale => + { + ScaleContainer.Scale = new Vector2(scale.NewValue * ScaleFactor); + }, true); + } + + protected override void OnApply() + { + base.OnApply(); + + HyperDash.BindTo(HitObject.HyperDashBindable); + ScaleBindable.BindTo(HitObject.ScaleBindable); + } + + protected override void OnFree() + { + HyperDash.UnbindFrom(HitObject.HyperDashBindable); + ScaleBindable.UnbindFrom(HitObject.ScaleBindable); + + base.OnFree(); } } } From dbf67f82c009d9bdea881424869c051783db5e3d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Nov 2020 11:40:38 +0900 Subject: [PATCH 231/394] Use bindable for DrawableDroplet HyperDash state --- .../Objects/Drawables/DrawableDroplet.cs | 12 +++++++++++- .../Objects/Drawables/Pieces/DropletPiece.cs | 7 ++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index 74cd240aa3..9e76265394 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -21,7 +21,17 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables [BackgroundDependencyLoader] private void load() { - ScaleContainer.Child = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.Droplet), _ => new DropletPiece()); + HyperDash.BindValueChanged(_ => updatePiece(), true); + } + + private void updatePiece() + { + ScaleContainer.Child = new SkinnableDrawable( + new CatchSkinComponent(CatchSkinComponents.Droplet), + _ => new DropletPiece + { + HyperDash = { BindTarget = HyperDash } + }); } protected override void UpdateInitialTransforms() diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/DropletPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/DropletPiece.cs index bcef30fda8..c90407ae15 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/DropletPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/DropletPiece.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; @@ -11,6 +12,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces { public class DropletPiece : CompositeDrawable { + public readonly Bindable HyperDash = new Bindable(); + public DropletPiece() { Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2); @@ -19,15 +22,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces [BackgroundDependencyLoader] private void load(DrawableHitObject drawableObject) { - var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject; - InternalChild = new Pulp { RelativeSizeAxes = Axes.Both, AccentColour = { BindTarget = drawableObject.AccentColour } }; - if (drawableCatchObject.HitObject.HyperDash) + if (HyperDash.Value) { AddInternal(new HyperDropletBorderPiece()); } From e36bb7631d0bc5abcd4ea82e4b90882afb322327 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Nov 2020 11:41:39 +0900 Subject: [PATCH 232/394] Fix colour not updated when index changes --- .../Objects/Drawables/DrawableFruit.cs | 17 ----------------- .../Drawables/DrawablePalpableCatchHitObject.cs | 8 ++++++++ .../Objects/Drawables/DrawableHitObject.cs | 6 +++--- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index d53bcd3a6f..e98f410724 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -12,8 +12,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { public class DrawableFruit : DrawablePalpableCatchHitObject { - public readonly Bindable IndexInBeatmap = new Bindable(); - public readonly Bindable VisualRepresentation = new Bindable(); protected virtual FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => (FruitVisualRepresentation)(indexInBeatmap % 4); @@ -23,7 +21,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public DrawableFruit(CatchHitObject h) : base(h) { - IndexInBeatmap.Value = h.IndexInBeatmap; } [BackgroundDependencyLoader] @@ -51,20 +48,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables }); } - protected override void OnApply() - { - base.OnApply(); - - IndexInBeatmap.BindTo(HitObject.IndexInBeatmapBindable); - } - - protected override void OnFree() - { - IndexInBeatmap.UnbindFrom(HitObject.IndexInBeatmapBindable); - - base.OnFree(); - } - protected override void Update() { base.Update(); diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs index 95f90a3eee..f44e290627 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public Bindable ScaleBindable { get; } = new Bindable(1); + public readonly Bindable IndexInBeatmap = new Bindable(); + /// /// The multiplicative factor applied to scale relative to scale. /// @@ -41,6 +43,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables Origin = Anchor.Centre, Anchor = Anchor.Centre, }); + + IndexInBeatmap.Value = h.IndexInBeatmap; } [BackgroundDependencyLoader] @@ -55,6 +59,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { ScaleContainer.Scale = new Vector2(scale.NewValue * ScaleFactor); }, true); + + IndexInBeatmap.BindValueChanged(_ => UpdateComboColour()); } protected override void OnApply() @@ -63,12 +69,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables HyperDash.BindTo(HitObject.HyperDashBindable); ScaleBindable.BindTo(HitObject.ScaleBindable); + IndexInBeatmap.BindTo(HitObject.IndexInBeatmapBindable); } protected override void OnFree() { HyperDash.UnbindFrom(HitObject.HyperDashBindable); ScaleBindable.UnbindFrom(HitObject.ScaleBindable); + IndexInBeatmap.UnbindFrom(HitObject.IndexInBeatmapBindable); base.OnFree(); } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 9c799bcf32..182aa1dbe3 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.LoadComplete(); - comboIndexBindable.BindValueChanged(_ => updateComboColour(), true); + comboIndexBindable.BindValueChanged(_ => UpdateComboColour(), true); updateState(ArmedState.Idle, true); } @@ -533,7 +533,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.SkinChanged(skin, allowFallback); - updateComboColour(); + UpdateComboColour(); ApplySkin(skin, allowFallback); @@ -541,7 +541,7 @@ namespace osu.Game.Rulesets.Objects.Drawables updateState(State.Value, true); } - private void updateComboColour() + protected void UpdateComboColour() { if (!(HitObject is IHasComboInformation combo)) return; From de471a7e8459334d4105797b51f2cbcf130e049f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Nov 2020 11:42:14 +0900 Subject: [PATCH 233/394] Add test for dynamically changing catch fruits --- .../TestSceneFruitObjects.cs | 8 ++--- .../TestSceneFruitVisualChange.cs | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index 93ffb947e1..ee7ab27eed 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -30,13 +30,13 @@ namespace osu.Game.Rulesets.Catch.Tests } private Drawable createDrawableFruit(FruitVisualRepresentation rep, bool hyperdash = false) => - setProperties(new TestDrawableFruit(new Fruit(), rep), hyperdash); + SetProperties(new TestDrawableFruit(new Fruit(), rep), hyperdash); - private Drawable createDrawableDroplet(bool hyperdash = false) => setProperties(new DrawableDroplet(new Droplet()), hyperdash); + private Drawable createDrawableDroplet(bool hyperdash = false) => SetProperties(new DrawableDroplet(new Droplet()), hyperdash); - private Drawable createDrawableTinyDroplet() => setProperties(new DrawableTinyDroplet(new TinyDroplet())); + private Drawable createDrawableTinyDroplet() => SetProperties(new DrawableTinyDroplet(new TinyDroplet())); - private DrawableCatchHitObject setProperties(DrawableCatchHitObject d, bool hyperdash = false) + protected virtual DrawableCatchHitObject SetProperties(DrawableCatchHitObject d, bool hyperdash = false) { var hitObject = d.HitObject; hitObject.StartTime = 1000000000000; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs new file mode 100644 index 0000000000..4448e828e7 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class TestSceneFruitVisualChange : TestSceneFruitObjects + { + private readonly Bindable indexInBeatmap = new Bindable(); + private readonly Bindable hyperDash = new Bindable(); + + protected override void LoadComplete() + { + AddStep("fruit changes visual and hyper", () => SetContents(() => SetProperties(new DrawableFruit(new Fruit + { + IndexInBeatmapBindable = { BindTarget = indexInBeatmap }, + HyperDashBindable = { BindTarget = hyperDash }, + })))); + + AddStep("droplet changes hyper", () => SetContents(() => SetProperties(new DrawableDroplet(new Droplet + { + HyperDashBindable = { BindTarget = hyperDash }, + })))); + + Scheduler.AddDelayed(() => indexInBeatmap.Value++, 250, true); + Scheduler.AddDelayed(() => hyperDash.Value = !hyperDash.Value, 1000, true); + } + } +} From 35cd6674f62453170861d6dab2f738fbb66932ba Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Nov 2020 11:56:57 +0900 Subject: [PATCH 234/394] Fix tiny droplet scale factor --- .../Objects/Drawables/DrawableTinyDroplet.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs index ae775684d8..8c4d821b4a 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs @@ -1,21 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; - namespace osu.Game.Rulesets.Catch.Objects.Drawables { public class DrawableTinyDroplet : DrawableDroplet { + protected override float ScaleFactor => base.ScaleFactor / 2; + public DrawableTinyDroplet(TinyDroplet h) : base(h) { } - - [BackgroundDependencyLoader] - private void load() - { - ScaleContainer.Scale /= 2; - } } } From 7ce752391db81112da5ce10eec6b3c66de2d692b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Nov 2020 12:02:07 +0900 Subject: [PATCH 235/394] Make TestSceneFruitObjects show correct color --- .../TestSceneFruitObjects.cs | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index ee7ab27eed..c661977b03 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects; @@ -17,33 +16,48 @@ namespace osu.Game.Rulesets.Catch.Tests { base.LoadComplete(); - foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation))) - AddStep($"show {rep}", () => SetContents(() => createDrawableFruit(rep))); + AddStep("show pear", () => SetContents(() => createDrawableFruit(0))); + AddStep("show grape", () => SetContents(() => createDrawableFruit(1))); + AddStep("show pineapple / apple", () => SetContents(() => createDrawableFruit(2))); + AddStep("show raspberry / orange", () => SetContents(() => createDrawableFruit(3))); + + AddStep("show banana", () => SetContents(createDrawableBanana)); AddStep("show droplet", () => SetContents(() => createDrawableDroplet())); AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet)); - foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation))) - AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawableFruit(rep, true))); + AddStep("show hyperdash pear", () => SetContents(() => createDrawableFruit(0, true))); + AddStep("show hyperdash grape", () => SetContents(() => createDrawableFruit(1, true))); + AddStep("show hyperdash pineapple / apple", () => SetContents(() => createDrawableFruit(2, true))); + AddStep("show hyperdash raspberry / orange", () => SetContents(() => createDrawableFruit(3, true))); AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true))); } - private Drawable createDrawableFruit(FruitVisualRepresentation rep, bool hyperdash = false) => - SetProperties(new TestDrawableFruit(new Fruit(), rep), hyperdash); + private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) => + SetProperties(new DrawableFruit(new Fruit + { + IndexInBeatmap = indexInBeatmap, + HyperDashBindable = { Value = hyperdash } + })); - private Drawable createDrawableDroplet(bool hyperdash = false) => SetProperties(new DrawableDroplet(new Droplet()), hyperdash); + private Drawable createDrawableBanana() => + SetProperties(new DrawableBanana(new Banana())); + + private Drawable createDrawableDroplet(bool hyperdash = false) => + SetProperties(new DrawableDroplet(new Droplet + { + HyperDashBindable = { Value = hyperdash } + })); private Drawable createDrawableTinyDroplet() => SetProperties(new DrawableTinyDroplet(new TinyDroplet())); - protected virtual DrawableCatchHitObject SetProperties(DrawableCatchHitObject d, bool hyperdash = false) + protected virtual DrawableCatchHitObject SetProperties(DrawableCatchHitObject d) { var hitObject = d.HitObject; hitObject.StartTime = 1000000000000; hitObject.Scale = 1.5f; - - if (hyperdash) - ((PalpableCatchHitObject)hitObject).HyperDashTarget = new Banana(); + hitObject.Samples.Clear(); // otherwise crash due to samples not loaded d.Anchor = Anchor.Centre; d.RelativePositionAxes = Axes.None; @@ -55,18 +69,5 @@ namespace osu.Game.Rulesets.Catch.Tests }; return d; } - - public class TestDrawableFruit : DrawableFruit - { - private readonly FruitVisualRepresentation visualRepresentation; - - protected override FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => visualRepresentation; - - public TestDrawableFruit(Fruit fruit, FruitVisualRepresentation rep) - : base(fruit) - { - visualRepresentation = rep; - } - } } } From 6e40de58e9a17aa3374ff3722e43543c975f4a9f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Nov 2020 13:36:40 +0900 Subject: [PATCH 236/394] Use new OnAdd and OnRemove to invalidate DHO --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 24 +++++++++++++++++ .../Scrolling/ScrollingHitObjectContainer.cs | 27 ++++++++++++++++++- .../UI/Scrolling/ScrollingPlayfield.cs | 5 ---- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 5fbda305c8..ac5d281ddc 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -114,6 +114,7 @@ namespace osu.Game.Rulesets.UI bindStartTime(drawable); AddInternal(drawableMap[entry] = drawable, false); + OnAdd(drawable); HitObjectUsageBegan?.Invoke(entry.HitObject); } @@ -129,6 +130,7 @@ namespace osu.Game.Rulesets.UI drawableMap.Remove(entry); + OnRemove(drawable); unbindStartTime(drawable); RemoveInternal(drawable); @@ -147,10 +149,12 @@ namespace osu.Game.Rulesets.UI hitObject.OnRevertResult += onRevertResult; AddInternal(hitObject); + OnAdd(hitObject); } public virtual bool Remove(DrawableHitObject hitObject) { + OnRemove(hitObject); if (!RemoveInternal(hitObject)) return false; @@ -178,6 +182,26 @@ namespace osu.Game.Rulesets.UI #endregion + /// + /// Invoked when a is added to this container. + /// + /// + /// This method is not invoked for nested s. + /// + protected virtual void OnAdd(DrawableHitObject drawableHitObject) + { + } + + /// + /// Invoked when a is removed from this container. + /// + /// + /// This method is not invoked for nested s. + /// + protected virtual void OnRemove(DrawableHitObject drawableHitObject) + { + } + public virtual void Clear(bool disposeChildren = true) { lifetimeManager.ClearEntries(); diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 02ee39e1b8..71f8f95300 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// Make this lifetime and layout computed in next update. /// - internal void InvalidateHitObject(DrawableHitObject hitObject) + private void invalidateHitObject(DrawableHitObject hitObject) { // Lifetime computation is delayed until next update because // when the hit object is not pooled this container is not loaded here and `scrollLength` cannot be computed. @@ -158,6 +158,31 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutComputed.Remove(hitObject); } + private void onAddRecursive(DrawableHitObject hitObject) + { + invalidateHitObject(hitObject); + + hitObject.DefaultsApplied += invalidateHitObject; + + foreach (var nested in hitObject.NestedHitObjects) + onAddRecursive(nested); + } + + protected override void OnAdd(DrawableHitObject drawableHitObject) => onAddRecursive(drawableHitObject); + + private void onRemoveRecursive(DrawableHitObject hitObject) + { + toComputeLifetime.Remove(hitObject); + layoutComputed.Remove(hitObject); + + hitObject.DefaultsApplied -= invalidateHitObject; + + foreach (var nested in hitObject.NestedHitObjects) + onRemoveRecursive(nested); + } + + protected override void OnRemove(DrawableHitObject drawableHitObject) => onRemoveRecursive(drawableHitObject); + private float scrollLength; protected override void Update() diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index 8aba896b34..2b75f93f9e 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -26,11 +26,6 @@ namespace osu.Game.Rulesets.UI.Scrolling Direction.BindTo(ScrollingInfo.Direction); } - protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject) - { - drawableHitObject.HitObjectApplied += d => HitObjectContainer.InvalidateHitObject(d); - } - /// /// Given a position in screen space, return the time within this column. /// From 792934f2c4ee5617c8a7ca68e8840d3c14400fb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Nov 2020 13:54:36 +0900 Subject: [PATCH 237/394] Allow scroll type to be specified This brings back the ability for the carousel to scroll in a classic way. It turns out this is generally what we want for "seek" operations like "random", else it's quite hard to get the expected animation. I did experiment with applying the animation after the pooled panels are retrieved, but in a best-case scenario there is still a gap where no panels are displayed during the random seek operation. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 57 +++++++++++++++------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 2012d47fd3..4ce87927a1 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Select Scroll.Clear(false); itemsCache.Invalidate(); - scrollPositionCache.Invalidate(); + ScrollToSelected(); // apply any pending filter operation that may have been delayed (see applyActiveCriteria's scheduling behaviour when BeatmapSetsLoaded is false). FlushPendingFilterOperations(); @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Select private readonly List visibleItems = new List(); private readonly Cached itemsCache = new Cached(); - private readonly Cached scrollPositionCache = new Cached(); + private PendingScrollOperation pendingScrollOperation = PendingScrollOperation.None; public Bindable RightClickScrollingEnabled = new Bindable(); @@ -462,7 +462,7 @@ namespace osu.Game.Screens.Select itemsCache.Invalidate(); if (alwaysResetScrollPosition || !Scroll.UserScrolling) - ScrollToSelected(); + ScrollToSelected(true); } } @@ -471,7 +471,12 @@ namespace osu.Game.Screens.Select /// /// Scroll to the current . /// - public void ScrollToSelected() => scrollPositionCache.Invalidate(); + /// + /// Whether the scroll position should immediately be shifted to the target, delegating animation to visible panels. + /// This should be true for operations like filtering - where panels are changing visibility state - to avoid large jumps in animation. + /// + public void ScrollToSelected(bool immediate = false) => + pendingScrollOperation = immediate ? PendingScrollOperation.Immediate : PendingScrollOperation.Standard; #region Key / button selection logic @@ -481,12 +486,12 @@ namespace osu.Game.Screens.Select { case Key.Left: if (!e.Repeat) - beginRepeatSelection(() => SelectNext(-1, true), e.Key); + beginRepeatSelection(() => SelectNext(-1), e.Key); return true; case Key.Right: if (!e.Repeat) - beginRepeatSelection(() => SelectNext(1, true), e.Key); + beginRepeatSelection(() => SelectNext(), e.Key); return true; } @@ -574,9 +579,8 @@ namespace osu.Game.Screens.Select updateYPositions(); // if there is a pending scroll action we apply it without animation and transfer the difference in position to the panels. - // due to this, scroll application needs to be run immediately after y position updates. - // if this isn't the case, the on-screen pooling / display logic following will fail briefly. - if (!scrollPositionCache.IsValid) + // this is intentionally applied before updating the visible range below, to avoid animating new items (sourced from pool) from locations off-screen, as it looks bad. + if (pendingScrollOperation != PendingScrollOperation.None) updateScrollPosition(); // This data is consumed to find the currently displayable range. @@ -795,17 +799,27 @@ namespace osu.Game.Screens.Select firstScroll = false; } - // in order to simplify animation logic, rather than using the animated version of ScrollTo, - // we take the difference in scroll height and apply to all visible panels. - // this avoids edge cases like when the visible panels is reduced suddenly, causing ScrollContainer - // to enter clamp-special-case mode where it animates completely differently to normal. - float scrollChange = scrollTarget.Value - Scroll.Current; + switch (pendingScrollOperation) + { + case PendingScrollOperation.Standard: + Scroll.ScrollTo(scrollTarget.Value); + break; - Scroll.ScrollTo(scrollTarget.Value, false); - scrollPositionCache.Validate(); + case PendingScrollOperation.Immediate: + // in order to simplify animation logic, rather than using the animated version of ScrollTo, + // we take the difference in scroll height and apply to all visible panels. + // this avoids edge cases like when the visible panels is reduced suddenly, causing ScrollContainer + // to enter clamp-special-case mode where it animates completely differently to normal. + float scrollChange = scrollTarget.Value - Scroll.Current; - foreach (var i in Scroll.Children) - i.Y += scrollChange; + Scroll.ScrollTo(scrollTarget.Value, false); + + foreach (var i in Scroll.Children) + i.Y += scrollChange; + break; + } + + pendingScrollOperation = PendingScrollOperation.None; } } @@ -849,6 +863,13 @@ namespace osu.Game.Screens.Select item.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); } + private enum PendingScrollOperation + { + None, + Standard, + Immediate, + } + /// /// A carousel item strictly used for binary search purposes. /// From f29aa9c4fca5d157a6b610ddab4811b7734beee3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Nov 2020 14:34:12 +0900 Subject: [PATCH 238/394] Move taiko barlines to their own ScrollingHitObjectContainer to avoid being considered as a selectable object --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 35 +++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 120cf264c3..370760f03e 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.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.Allocation; using osu.Framework.Graphics; @@ -37,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.UI private SkinnableDrawable mascot; private ProxyContainer topLevelHitContainer; - private ProxyContainer barlineContainer; + private ScrollingHitObjectContainer barlineContainer; private Container rightArea; private Container leftArea; @@ -83,10 +84,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - barlineContainer = new ProxyContainer - { - RelativeSizeAxes = Axes.Both, - }, + barlineContainer = new ScrollingHitObjectContainer(), new Container { Name = "Hit objects", @@ -159,18 +157,37 @@ namespace osu.Game.Rulesets.Taiko.UI public override void Add(DrawableHitObject h) { - h.OnNewResult += OnNewResult; - base.Add(h); - switch (h) { case DrawableBarLine barline: - barlineContainer.Add(barline.CreateProxy()); + barlineContainer.Add(barline); break; case DrawableTaikoHitObject taikoObject: + h.OnNewResult += OnNewResult; topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); + base.Add(h); break; + + default: + throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type"); + } + } + + public override bool Remove(DrawableHitObject h) + { + switch (h) + { + case DrawableBarLine barline: + return barlineContainer.Remove(barline); + + case DrawableTaikoHitObject _: + h.OnNewResult -= OnNewResult; + // todo: consider tidying of proxied content if required. + return base.Remove(h); + + default: + throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type"); } } From b9b885798801e8737498704e2186bec8fe985d58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Nov 2020 15:11:07 +0900 Subject: [PATCH 239/394] 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 6dab6edc5e..0b43fd73f5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 54f3fcede6..e201383d51 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 692dac909a..e5f7581404 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - + From 1246c8ba5f9b15bef09ff8c6b0a1fd208ddbb8d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Nov 2020 15:22:28 +0900 Subject: [PATCH 240/394] Reduce the opacity of the default skin slider ball Previous value was [hitting pure white on some brighter combo colours](https://github.com/ppy/osu/issues/10910#issuecomment-734354812). --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index c5bf790377..ca5ca7ac59 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -248,7 +248,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } private void trackingChanged(ValueChangedEvent tracking) => - box.FadeTo(tracking.NewValue ? 0.6f : 0.05f, 200, Easing.OutQuint); + box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint); } } } From a7194e1bc347a204661c939517067cf931c997b6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Nov 2020 15:41:35 +0900 Subject: [PATCH 241/394] add stateless RNG --- osu.Game/Utils/StatelessRNG.cs | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 osu.Game/Utils/StatelessRNG.cs diff --git a/osu.Game/Utils/StatelessRNG.cs b/osu.Game/Utils/StatelessRNG.cs new file mode 100644 index 0000000000..8d08b26ca4 --- /dev/null +++ b/osu.Game/Utils/StatelessRNG.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Utils +{ + /// Provides a fast stateless function that can be used in randomly-looking visual elements. + public static class StatelessRNG + { + private static ulong mix(ulong x) + { + unchecked + { + x ^= x >> 33; + x *= 0xff51afd7ed558ccd; + x ^= x >> 33; + x *= 0xc4ceb9fe1a85ec53; + x ^= x >> 33; + return x; + } + } + + /// Compute an integer from given seed and series number. + /// + /// The seed value of this random number generator. + /// + /// + /// The series number. + /// Different values are computed for the same seed in different series. + /// + public static ulong Get(int seed, int series = 0) => + unchecked(mix(((ulong)(uint)series << 32) | ((uint)seed ^ 0x12345678))); + + /// Compute a floating point value between 0 and 1 (excluding 1) from given seed and series number. + /// + /// The seed value of this random number generator. + /// + /// + /// The series number. + /// Different values are computed for the same seed in different series. + /// + public static float GetSingle(int seed, int series = 0) => + (float)(Get(seed, series) & ((1 << 24) - 1)) / (1 << 24); // float has 24-bit precision + } +} From 7edbba58f70d8d61ddfcb6e90d57db0424751f2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Nov 2020 16:28:29 +0900 Subject: [PATCH 242/394] Avoid updating hitobjects unnecessarily for start time changes This was firing regardless of whether the start time was changed, such as where beat snap provided the same time the object already has. The case where a change actually occurs is already handled by EditorBeatmap (see `startTimeBindables`), so it turns out this local handling is not required at all. --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index def5f396f1..57f9a7f221 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -496,10 +496,7 @@ namespace osu.Game.Screens.Edit.Compose.Components double offset = result.Time.Value - movementBlueprints.First().HitObject.StartTime; foreach (HitObject obj in Beatmap.SelectedHitObjects) - { obj.StartTime += offset; - Beatmap.Update(obj); - } } return true; From 18bb0cb45b944dbcb2afee1673ec1601702d6249 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Nov 2020 16:31:59 +0900 Subject: [PATCH 243/394] Remove unnecessary schedule logic from Apply's local updateState call There were cases in the editor where rewinding of transforms would leave the `DrawableHitObject` in a non-`IsPresent` state, resulting in this scheduled logic never running. This would in turn cause ghost hitobjects, which disappear under certain circumstances. Reproduction: - Open editor to empty beatmap - Place single hitcircle at current point in time - Drag editor timeline backwards to seek before zero, and wait for return to zero - Select hitcircle in playfield - Drag hitcircle to right in timeline, triggering a start time change --- .../Objects/Drawables/DrawableHitObject.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 9c799bcf32..95bc72edf6 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -263,18 +263,15 @@ namespace osu.Game.Rulesets.Objects.Drawables OnApply(); HitObjectApplied?.Invoke(this); - // If not loaded, the state update happens in LoadComplete(). Otherwise, the update is scheduled to allow for lifetime updates. + // If not loaded, the state update happens in LoadComplete(). if (IsLoaded) { - Scheduler.Add(() => - { - if (Result.IsHit) - updateState(ArmedState.Hit, true); - else if (Result.HasResult) - updateState(ArmedState.Miss, true); - else - updateState(ArmedState.Idle, true); - }); + if (Result.IsHit) + updateState(ArmedState.Hit, true); + else if (Result.HasResult) + updateState(ArmedState.Miss, true); + else + updateState(ArmedState.Idle, true); } hasHitObjectApplied = true; From a9c59eed02edba7a43c5d2bebaf21c8a25f7e3cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Nov 2020 16:56:26 +0900 Subject: [PATCH 244/394] Add test coverage of fail scenario --- .../Editing/EditorChangeHandlerTest.cs | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs index b7a41ffd1c..5064b0fd22 100644 --- a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs +++ b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; @@ -44,6 +43,36 @@ namespace osu.Game.Tests.Editing Assert.That(stateChangedFired, Is.EqualTo(2)); } + [Test] + public void TestApplyThenUndoThenApplySameChange() + { + var (handler, beatmap) = createChangeHandler(); + + Assert.That(handler.CanUndo.Value, Is.False); + Assert.That(handler.CanRedo.Value, Is.False); + + string originalHash = handler.CurrentStateHash; + + addArbitraryChange(beatmap); + handler.SaveState(); + + Assert.That(handler.CanUndo.Value, Is.True); + Assert.That(handler.CanRedo.Value, Is.False); + Assert.That(stateChangedFired, Is.EqualTo(1)); + + string hash = handler.CurrentStateHash; + + // save a save without making any changes + handler.RestoreState(-1); + + Assert.That(originalHash, Is.EqualTo(handler.CurrentStateHash)); + Assert.That(stateChangedFired, Is.EqualTo(2)); + + addArbitraryChange(beatmap); + handler.SaveState(); + Assert.That(hash, Is.EqualTo(handler.CurrentStateHash)); + } + [Test] public void TestSaveSameStateDoesNotSave() { @@ -139,7 +168,7 @@ namespace osu.Game.Tests.Editing private void addArbitraryChange(EditorBeatmap beatmap) { - beatmap.Add(new HitCircle { StartTime = RNG.Next(0, 100000) }); + beatmap.Add(new HitCircle { StartTime = 2760 }); } } } From 7e34c5e239e2e9fed537ce99cde4f7b3311a6469 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Nov 2020 16:57:11 +0900 Subject: [PATCH 245/394] Fix state application always checking newest state for early abort, rather than current --- osu.Game/Screens/Edit/EditorChangeHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 62187aed24..2dcb416a03 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Edit var newState = stream.ToArray(); // if the previous state is binary equal we don't need to push a new one, unless this is the initial state. - if (savedStates.Count > 0 && newState.SequenceEqual(savedStates.Last())) return; + if (savedStates.Count > 0 && newState.SequenceEqual(savedStates[currentState])) return; if (currentState < savedStates.Count - 1) savedStates.RemoveRange(currentState + 1, savedStates.Count - currentState - 1); From 5bc76cac58a1e0690e9df0d6cd165841f9a3e736 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Nov 2020 17:01:07 +0900 Subject: [PATCH 246/394] Remove unused using statement --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index cf9908decd..628d95dff4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.UI; using osuTK; From 8ad4cf73f5c17fca110486de442a83cf11074473 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 28 Nov 2020 17:07:29 +0200 Subject: [PATCH 247/394] Scale stars from 0.4 to 1 --- osu.Game/Graphics/UserInterface/StarCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index b13d6485ac..f249156a59 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -147,7 +147,7 @@ namespace osu.Game.Graphics.UserInterface public override void DisplayAt(float scale) { - scale = Math.Clamp(scale, min_star_scale, 1); + scale = Math.Clamp(scale * (1 - min_star_scale) + min_star_scale, min_star_scale, 1); this.FadeTo(scale, fading_duration); Icon.ScaleTo(scale, scaling_duration, scaling_easing); From 8e0f525588a5d0e997b5a15da50045ebbb719434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Nov 2020 20:29:35 +0100 Subject: [PATCH 248/394] Rewrite existing test scene somewhat --- .../Visual/Gameplay/TestSceneStarCounter.cs | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs index 709e71d195..d6a6ef712a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -14,44 +13,40 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneStarCounter : OsuTestScene { + private readonly StarCounter starCounter; + private readonly OsuSpriteText starsLabel; + public TestSceneStarCounter() { - StarCounter stars = new StarCounter + starCounter = new StarCounter { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Current = 5, }; - Add(stars); + Add(starCounter); - SpriteText starsLabel = new OsuSpriteText + starsLabel = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, Scale = new Vector2(2), Y = 50, - Text = stars.Current.ToString("0.00"), }; Add(starsLabel); - AddRepeatStep(@"random value", delegate - { - stars.Current = RNG.NextSingle() * (stars.StarCount + 1); - starsLabel.Text = stars.Current.ToString("0.00"); - }, 10); + setStars(5); - AddStep(@"Stop animation", delegate - { - stars.StopAnimation(); - }); + AddRepeatStep("random value", () => setStars(RNG.NextSingle() * (starCounter.StarCount + 1)), 10); + AddStep("stop animation", () => starCounter.StopAnimation()); + AddStep("reset", () => setStars(0)); + } - AddStep(@"Reset", delegate - { - stars.Current = 0; - starsLabel.Text = stars.Current.ToString("0.00"); - }); + private void setStars(float stars) + { + starCounter.Current = stars; + starsLabel.Text = starCounter.Current.ToString("0.00"); } } } From 9bf70e4e97ed84c95bb5d19eb4e7cf59391f7e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Nov 2020 20:32:08 +0100 Subject: [PATCH 249/394] Add slider test step for visual inspection purposes --- osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs index d6a6ef712a..717485bcc1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs @@ -39,6 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay setStars(5); AddRepeatStep("random value", () => setStars(RNG.NextSingle() * (starCounter.StarCount + 1)), 10); + AddSliderStep("exact value", 0f, 10f, 5f, setStars); AddStep("stop animation", () => starCounter.StopAnimation()); AddStep("reset", () => setStars(0)); } From a3afd88387b0d4cb939adf2ff69cd3031672d06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Nov 2020 20:35:03 +0100 Subject: [PATCH 250/394] Use Interpolation.Lerp --- osu.Game/Graphics/UserInterface/StarCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index f249156a59..894a21fcf3 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -147,7 +147,7 @@ namespace osu.Game.Graphics.UserInterface public override void DisplayAt(float scale) { - scale = Math.Clamp(scale * (1 - min_star_scale) + min_star_scale, min_star_scale, 1); + scale = (float)Interpolation.Lerp(min_star_scale, 1, Math.Clamp(scale, 0, 1)); this.FadeTo(scale, fading_duration); Icon.ScaleTo(scale, scaling_duration, scaling_easing); From a5c4a8d2e9ae59d133547c9f1748c135ec87eb5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 29 Nov 2020 22:00:15 +0100 Subject: [PATCH 251/394] Create "User Interface" settings section --- .../Settings/Sections/AudioSection.cs | 1 - .../Settings/Sections/GameplaySection.cs | 1 - .../Settings/Sections/GraphicsSection.cs | 1 - .../GeneralSettings.cs} | 6 ++-- .../MainMenuSettings.cs | 2 +- .../SongSelectSettings.cs | 2 +- .../Settings/Sections/UserInterfaceSection.cs | 29 +++++++++++++++++++ osu.Game/Overlays/SettingsOverlay.cs | 1 + 8 files changed, 35 insertions(+), 8 deletions(-) rename osu.Game/Overlays/Settings/Sections/{Graphics/UserInterfaceSettings.cs => UserInterface/GeneralSettings.cs} (88%) rename osu.Game/Overlays/Settings/Sections/{Audio => UserInterface}/MainMenuSettings.cs (97%) rename osu.Game/Overlays/Settings/Sections/{Gameplay => UserInterface}/SongSelectSettings.cs (97%) create mode 100644 osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index 69538358f1..7072d8e63d 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -27,7 +27,6 @@ namespace osu.Game.Overlays.Settings.Sections new AudioDevicesSettings(), new VolumeSettings(), new OffsetSettings(), - new MainMenuSettings() }; } } diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index e5cebd28e2..acb94a6a01 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -26,7 +26,6 @@ namespace osu.Game.Overlays.Settings.Sections Children = new Drawable[] { new GeneralSettings(), - new SongSelectSettings(), new ModsSettings(), }; } diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs index c1b4b0bbcb..4ade48031f 100644 --- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs @@ -23,7 +23,6 @@ namespace osu.Game.Overlays.Settings.Sections new RendererSettings(), new LayoutSettings(), new DetailSettings(), - new UserInterfaceSettings(), }; } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs similarity index 88% rename from osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs rename to osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index 38c30ddd64..419d2e83ad 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -6,11 +6,11 @@ using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Overlays.Settings.Sections.Graphics +namespace osu.Game.Overlays.Settings.Sections.UserInterface { - public class UserInterfaceSettings : SettingsSubsection + public class GeneralSettings : SettingsSubsection { - protected override string Header => "User Interface"; + protected override string Header => "General"; [BackgroundDependencyLoader] private void load(OsuConfigManager config) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs similarity index 97% rename from osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs rename to osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs index 7682967d10..598b666642 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs @@ -7,7 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; -namespace osu.Game.Overlays.Settings.Sections.Audio +namespace osu.Game.Overlays.Settings.Sections.UserInterface { public class MainMenuSettings : SettingsSubsection { diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs similarity index 97% rename from osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs rename to osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index b26876556e..c73a783d37 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Overlays.Settings.Sections.Gameplay +namespace osu.Game.Overlays.Settings.Sections.UserInterface { public class SongSelectSettings : SettingsSubsection { diff --git a/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs b/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs new file mode 100644 index 0000000000..718fea5f2b --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays.Settings.Sections.UserInterface; + +namespace osu.Game.Overlays.Settings.Sections +{ + public class UserInterfaceSection : SettingsSection + { + public override string Header => "User Interface"; + + public override Drawable CreateIcon() => new SpriteIcon + { + Icon = FontAwesome.Solid.LayerGroup + }; + + public UserInterfaceSection() + { + Children = new Drawable[] + { + new GeneralSettings(), + new MainMenuSettings(), + new SongSelectSettings() + }; + } + } +} diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index e1bcdbbaf0..f05d82cb6c 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -23,6 +23,7 @@ namespace osu.Game.Overlays { new GeneralSection(), new GraphicsSection(), + new UserInterfaceSection(), new GameplaySection(), new AudioSection(), new SkinSection(), From e0a84ff1dc174ec206ff12d094639bc670c5b6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 29 Nov 2020 22:03:56 +0100 Subject: [PATCH 252/394] Move hold-to-confirm setting back to gameplay section --- .../Settings/Sections/Gameplay/GeneralSettings.cs | 12 ++++++++++++ .../Sections/UserInterface/GeneralSettings.cs | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index be464fa2b7..53f1a0b4ba 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -5,6 +5,7 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Scoring; namespace osu.Game.Overlays.Settings.Sections.Gameplay @@ -63,6 +64,12 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Always show key overlay", Current = config.GetBindable(OsuSetting.KeyOverlay) }, + new SettingsSlider + { + LabelText = "Hold-to-confirm activation time", + Current = config.GetBindable(OsuSetting.UIHoldActivationDelay), + KeyboardStep = 50 + }, new SettingsCheckbox { LabelText = "Positional hitsounds", @@ -95,5 +102,10 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay }); } } + + private class TimeSlider : OsuSliderBar + { + public override string TooltipText => Current.Value.ToString("N0") + "ms"; + } } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index 419d2e83ad..797e00a147 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections.UserInterface { @@ -27,18 +26,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface LabelText = "Parallax", Current = config.GetBindable(OsuSetting.MenuParallax) }, - new SettingsSlider - { - LabelText = "Hold-to-confirm activation time", - Current = config.GetBindable(OsuSetting.UIHoldActivationDelay), - KeyboardStep = 50 - }, }; } - - private class TimeSlider : OsuSliderBar - { - public override string TooltipText => Current.Value.ToString("N0") + "ms"; - } } } From 3994cf082d835f9cb61dc687c2270bd800af2514 Mon Sep 17 00:00:00 2001 From: Ryan Zmuda Date: Sun, 29 Nov 2020 20:59:02 -0500 Subject: [PATCH 253/394] add keybind for in game overlay --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- .../Input/Bindings/GlobalActionContainer.cs | 4 +++ osu.Game/Screens/Play/HUDOverlay.cs | 31 +++++++++++-------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 89a6ee8b07..a07e446d2e 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -184,7 +184,7 @@ namespace osu.Game.Configuration return new TrackedSettings { new TrackedSetting(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled", LookupKeyBindings(GlobalAction.ToggleGameplayMouseButtons))), - new TrackedSetting(OsuSetting.HUDVisibilityMode, m => new SettingDescription(m, "HUD Visibility", m.GetDescription(), $"cycle: shift-tab quick view: {LookupKeyBindings(GlobalAction.HoldForHUD)}")), + new TrackedSetting(OsuSetting.HUDVisibilityMode, m => new SettingDescription(m, "HUD Visibility", m.GetDescription(), $"cycle: {LookupKeyBindings(GlobalAction.ToggleInGameInterface)} quick view: {LookupKeyBindings(GlobalAction.HoldForHUD)}")), new TrackedSetting(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())), new TrackedSetting(OsuSetting.Skin, m => { diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 74eb2b0126..a59d69e5b5 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -68,6 +68,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Tilde }, GlobalAction.QuickExit), new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), + new KeyBinding(InputKey.I, GlobalAction.ToggleInGameInterface), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), new KeyBinding(InputKey.Space, GlobalAction.TogglePauseReplay), new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), @@ -145,6 +146,9 @@ namespace osu.Game.Input.Bindings [Description("Decrease scroll speed")] DecreaseScrollSpeed, + [Description("Toggle in-game interface")] + ToggleInGameInterface, + [Description("Select")] Select, diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index e83dded075..964e0d0536 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -282,20 +282,7 @@ namespace osu.Game.Screens.Play switch (e.Key) { case Key.Tab: - switch (configVisibilityMode.Value) - { - case HUDVisibilityMode.Never: - configVisibilityMode.Value = HUDVisibilityMode.HideDuringGameplay; - break; - case HUDVisibilityMode.HideDuringGameplay: - configVisibilityMode.Value = HUDVisibilityMode.Always; - break; - - case HUDVisibilityMode.Always: - configVisibilityMode.Value = HUDVisibilityMode.Never; - break; - } return true; } @@ -377,6 +364,24 @@ namespace osu.Game.Screens.Play holdingForHUD = true; updateVisibility(); return true; + + case GlobalAction.ToggleInGameInterface: + switch (configVisibilityMode.Value) + { + case HUDVisibilityMode.Never: + configVisibilityMode.Value = HUDVisibilityMode.HideDuringGameplay; + break; + + case HUDVisibilityMode.HideDuringGameplay: + configVisibilityMode.Value = HUDVisibilityMode.Always; + break; + + case HUDVisibilityMode.Always: + configVisibilityMode.Value = HUDVisibilityMode.Never; + break; + } + updateVisibility(); + return true; } return false; From a780a8bbd8db3e3e8bd8737480de1503df6f81b8 Mon Sep 17 00:00:00 2001 From: Ryan Zmuda Date: Sun, 29 Nov 2020 21:52:58 -0500 Subject: [PATCH 254/394] forgot to remove something... sorry --- osu.Game/Screens/Play/HUDOverlay.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 964e0d0536..457706b8f5 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -273,24 +273,6 @@ namespace osu.Game.Screens.Play Progress.BindDrawableRuleset(drawableRuleset); } - protected override bool OnKeyDown(KeyDownEvent e) - { - if (e.Repeat) return false; - - if (e.ShiftPressed) - { - switch (e.Key) - { - case Key.Tab: - - - return true; - } - } - - return base.OnKeyDown(e); - } - protected virtual SkinnableAccuracyCounter CreateAccuracyCounter() => new SkinnableAccuracyCounter(); protected virtual SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter(); From 5d3a5081a0548f98d485c4dfe8d879e70536159d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 12:52:58 +0900 Subject: [PATCH 255/394] Remove use of HitObject in DHO constructors. --- .../Objects/Drawables/DrawableCatchHitObject.cs | 6 ++---- .../Objects/Drawables/DrawablePalpableCatchHitObject.cs | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 92b4f7cae2..1faa6a5b0f 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.UI; @@ -19,12 +20,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH; - protected DrawableCatchHitObject(CatchHitObject hitObject) + protected DrawableCatchHitObject([CanBeNull] CatchHitObject hitObject) : base(hitObject) { - if (hitObject != null) - XBindable.Value = hitObject.X; - Anchor = Anchor.BottomLeft; } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs index f44e290627..128d81aca4 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -31,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables protected readonly Container ScaleContainer; - protected DrawablePalpableCatchHitObject(CatchHitObject h) + protected DrawablePalpableCatchHitObject([CanBeNull] CatchHitObject h) : base(h) { Origin = Anchor.Centre; @@ -43,8 +44,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables Origin = Anchor.Centre, Anchor = Anchor.Centre, }); - - IndexInBeatmap.Value = h.IndexInBeatmap; } [BackgroundDependencyLoader] From 7986d7802df3d39f976f9ff71cef2dea6fcedf60 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 12:58:14 +0900 Subject: [PATCH 256/394] Use `ApplyDefaults` in `TestSceneFruitObjects`. --- osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index c661977b03..160da75aa9 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -3,6 +3,8 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osuTK; @@ -55,9 +57,9 @@ namespace osu.Game.Rulesets.Catch.Tests protected virtual DrawableCatchHitObject SetProperties(DrawableCatchHitObject d) { var hitObject = d.HitObject; + hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 0 }); hitObject.StartTime = 1000000000000; hitObject.Scale = 1.5f; - hitObject.Samples.Clear(); // otherwise crash due to samples not loaded d.Anchor = Anchor.Centre; d.RelativePositionAxes = Axes.None; From 09b7ba41d6632da36896b2b79371aa54e601961e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 13:00:01 +0900 Subject: [PATCH 257/394] Consistently use readonly field for bindables. --- .../Objects/Drawables/DrawablePalpableCatchHitObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs index 128d81aca4..a3908f94b6 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs @@ -14,9 +14,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject; - public Bindable HyperDash { get; } = new Bindable(); + public readonly Bindable HyperDash = new Bindable(); - public Bindable ScaleBindable { get; } = new Bindable(1); + public readonly Bindable ScaleBindable = new Bindable(1); public readonly Bindable IndexInBeatmap = new Bindable(); From 5e0e4e9db793af739f78794dfd9cab7268bdd8fc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 13:06:04 +0900 Subject: [PATCH 258/394] Use private access modifier for `Border` field. --- osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs index a9a8f551ce..f7d931ad5b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces public readonly Bindable VisualRepresentation = new Bindable(); public readonly Bindable HyperDash = new Bindable(); - public BorderPiece Border; + public BorderPiece Border { get; private set; } public FruitPiece() { From 6478bed431ec467b5c3579a3d264b8719a6b1f81 Mon Sep 17 00:00:00 2001 From: Ryan Zmuda Date: Sun, 29 Nov 2020 23:14:43 -0500 Subject: [PATCH 259/394] Revert "forgot to remove something... sorry" This reverts commit a780a8bbd8db3e3e8bd8737480de1503df6f81b8. --- osu.Game/Screens/Play/HUDOverlay.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 457706b8f5..964e0d0536 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -273,6 +273,24 @@ namespace osu.Game.Screens.Play Progress.BindDrawableRuleset(drawableRuleset); } + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Repeat) return false; + + if (e.ShiftPressed) + { + switch (e.Key) + { + case Key.Tab: + + + return true; + } + } + + return base.OnKeyDown(e); + } + protected virtual SkinnableAccuracyCounter CreateAccuracyCounter() => new SkinnableAccuracyCounter(); protected virtual SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter(); From 9145557522e94e53aa4a0c74f0cc534d23329847 Mon Sep 17 00:00:00 2001 From: Ryan Zmuda Date: Sun, 29 Nov 2020 23:15:12 -0500 Subject: [PATCH 260/394] Revert "Revert "forgot to remove something... sorry"" This reverts commit 6478bed431ec467b5c3579a3d264b8719a6b1f81. --- osu.Game/Screens/Play/HUDOverlay.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 964e0d0536..457706b8f5 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -273,24 +273,6 @@ namespace osu.Game.Screens.Play Progress.BindDrawableRuleset(drawableRuleset); } - protected override bool OnKeyDown(KeyDownEvent e) - { - if (e.Repeat) return false; - - if (e.ShiftPressed) - { - switch (e.Key) - { - case Key.Tab: - - - return true; - } - } - - return base.OnKeyDown(e); - } - protected virtual SkinnableAccuracyCounter CreateAccuracyCounter() => new SkinnableAccuracyCounter(); protected virtual SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter(); From 6bea78619a9c59aefcbd47d935233fc61496031e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 30 Nov 2020 13:33:29 +0900 Subject: [PATCH 261/394] Update comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Tests/Editing/EditorChangeHandlerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs index 5064b0fd22..481cb3230e 100644 --- a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs +++ b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Editing string hash = handler.CurrentStateHash; - // save a save without making any changes + // undo a change without saving handler.RestoreState(-1); Assert.That(originalHash, Is.EqualTo(handler.CurrentStateHash)); From 4228977c866becfb4710349cf86c654d166a4817 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 13:46:02 +0900 Subject: [PATCH 262/394] Store a DHO in `FruitPiece` to animate itself. --- .../Objects/Drawables/DrawableFruit.cs | 12 +------ .../Objects/Drawables/Pieces/FruitPiece.cs | 33 +++++++++++++------ 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index e98f410724..4338d80345 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -16,8 +16,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables protected virtual FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => (FruitVisualRepresentation)(indexInBeatmap % 4); - private FruitPiece fruitPiece; - public DrawableFruit(CatchHitObject h) : base(h) { @@ -41,21 +39,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { ScaleContainer.Child = new SkinnableDrawable( new CatchSkinComponent(getComponent(VisualRepresentation.Value)), - _ => fruitPiece = new FruitPiece + _ => new FruitPiece { VisualRepresentation = { BindTarget = VisualRepresentation }, HyperDash = { BindTarget = HyperDash }, }); } - protected override void Update() - { - base.Update(); - - if (fruitPiece != null) - fruitPiece.Border.Alpha = (float)Math.Clamp((StartTimeBindable.Value - Time.Current) / 500, 0, 1); - } - private CatchSkinComponents getComponent(FruitVisualRepresentation hitObjectVisualRepresentation) { switch (hitObjectVisualRepresentation) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs index f7d931ad5b..25fc53ce21 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs @@ -1,10 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces { @@ -18,26 +21,36 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces public readonly Bindable VisualRepresentation = new Bindable(); public readonly Bindable HyperDash = new Bindable(); - public BorderPiece Border { get; private set; } + [CanBeNull] + private DrawableCatchHitObject drawableHitObject; + + [CanBeNull] + private BorderPiece borderPiece; public FruitPiece() { RelativeSizeAxes = Axes.Both; } - [BackgroundDependencyLoader] - private void load() + [BackgroundDependencyLoader(permitNulls: true)] + private void load([CanBeNull] DrawableHitObject drawable) { - AddRangeInternal(new[] - { - getFruitFor(VisualRepresentation.Value), - Border = new BorderPiece(), - }); + drawableHitObject = (DrawableCatchHitObject)drawable; + + AddInternal(getFruitFor(VisualRepresentation.Value)); + + // if it is not part of a DHO, the border is always invisible. + if (drawableHitObject != null) + AddInternal(borderPiece = new BorderPiece()); if (HyperDash.Value) - { AddInternal(new HyperBorderPiece()); - } + } + + protected override void Update() + { + if (borderPiece != null && drawableHitObject.HitObject != null) + borderPiece.Alpha = (float)Math.Clamp((drawableHitObject.HitObject.StartTime - Time.Current) / 500, 0, 1); } private Drawable getFruitFor(FruitVisualRepresentation representation) From 8528b2687f922d6013c720bf8ca6cb4990e95ea8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 14:24:50 +0900 Subject: [PATCH 263/394] Fix possible null reference. --- osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs index 25fc53ce21..31487ee407 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/FruitPiece.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces protected override void Update() { - if (borderPiece != null && drawableHitObject.HitObject != null) + if (borderPiece != null && drawableHitObject?.HitObject != null) borderPiece.Alpha = (float)Math.Clamp((drawableHitObject.HitObject.StartTime - Time.Current) / 500, 0, 1); } From 73990a6674a4d4037574bf569c3b361b857d9dc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 15:20:52 +0900 Subject: [PATCH 264/394] Fix osu!catch combo counter not showing after 1 combo --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index abbdeacd9a..f2f783a11c 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -55,6 +55,11 @@ namespace osu.Game.Rulesets.Catch.UI HitObjectContainer, CatcherArea, }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); NewResult += onNewResult; RevertResult += onRevertResult; From 9fbfb1aa9fd2fd766c066c0b987ee4089b5c2066 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 15:22:55 +0900 Subject: [PATCH 265/394] Add comment explaining requirement --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index f2f783a11c..9df32d8d36 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -61,6 +61,7 @@ namespace osu.Game.Rulesets.Catch.UI { base.LoadComplete(); + // these subscriptions need to be done post constructor to ensure externally bound components have a chance to populate required fields (ScoreProcessor / ComboAtJudgement in this case). NewResult += onNewResult; RevertResult += onRevertResult; } From 809caaa44c72f72f5b64bf67a537ff6303d0a16a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 15:39:08 +0900 Subject: [PATCH 266/394] Use standard switch syntax (preferred for now) --- .../TestSceneDrawableScrollingRuleset.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 257ae10d82..8da2b58c1e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -302,14 +302,21 @@ namespace osu.Game.Tests.Visual.Gameplay TimeRange.Value = time_range; } - public override DrawableHitObject CreateDrawableRepresentation(TestHitObject h) => - h switch + public override DrawableHitObject CreateDrawableRepresentation(TestHitObject h) + { + switch (h) { - TestPooledHitObject _ => null, - TestPooledParentHitObject _ => null, - TestParentHitObject p => new DrawableTestParentHitObject(p), - _ => new DrawableTestHitObject(h), - }; + case TestPooledHitObject _: + case TestPooledParentHitObject _: + return null; + + case TestParentHitObject p: + return new DrawableTestParentHitObject(p); + + default: + return new DrawableTestHitObject(h); + } + } protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager(); From 31cfaefdeb57bb967555c225804db5309e979592 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 15:39:43 +0900 Subject: [PATCH 267/394] Move private functions in line with others --- .../TestSceneDrawableScrollingRuleset.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 8da2b58c1e..9931ee4a45 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -90,22 +90,6 @@ namespace osu.Game.Tests.Visual.Gameplay assertChildPosition(5); } - private void assertDead(int index) => AddAssert($"hitobject {index} is dead", () => getDrawableHitObject(index) == null); - - private void assertHeight(int index) => AddAssert($"hitobject {index} height", () => - { - var d = getDrawableHitObject(index); - return d != null && Precision.AlmostEquals(d.DrawHeight, yScale * (float)(d.HitObject.Duration / time_range), 0.1f); - }); - - private void assertChildPosition(int index) => AddAssert($"hitobject {index} child position", () => - { - var d = getDrawableHitObject(index); - return d is DrawableTestParentHitObject && Precision.AlmostEquals( - d.NestedHitObjects.First().DrawPosition.Y, - yScale * (float)((TestParentHitObject)d.HitObject).ChildTimeOffset / time_range, 0.1f); - }); - [Test] public void TestRelativeBeatLengthScaleSingleTimingPoint() { @@ -220,6 +204,22 @@ namespace osu.Game.Tests.Visual.Gameplay private float yScale => drawableRuleset.Playfield.HitObjectContainer.DrawHeight; + private void assertDead(int index) => AddAssert($"hitobject {index} is dead", () => getDrawableHitObject(index) == null); + + private void assertHeight(int index) => AddAssert($"hitobject {index} height", () => + { + var d = getDrawableHitObject(index); + return d != null && Precision.AlmostEquals(d.DrawHeight, yScale * (float)(d.HitObject.Duration / time_range), 0.1f); + }); + + private void assertChildPosition(int index) => AddAssert($"hitobject {index} child position", () => + { + var d = getDrawableHitObject(index); + return d is DrawableTestParentHitObject && Precision.AlmostEquals( + d.NestedHitObjects.First().DrawPosition.Y, + yScale * (float)((TestParentHitObject)d.HitObject).ChildTimeOffset / time_range, 0.1f); + }); + private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}", () => Precision.AlmostEquals(getDrawableHitObject(index)?.DrawPosition.Y ?? -1, yScale * relativeY)); From 274565998653284abcdef19fc4be19f8e9dc937b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 15:54:20 +0900 Subject: [PATCH 268/394] Reword and xmldoc some comments --- .../UI/Scrolling/ScrollingHitObjectContainer.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 71f8f95300..6a77597916 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -17,11 +17,14 @@ namespace osu.Game.Rulesets.UI.Scrolling private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); - // The lifetime of a hit object in this will be computed in next update. + /// + /// Hit objects which require lifetime computation in the next update call. + /// private readonly HashSet toComputeLifetime = new HashSet(); - // The layout (length if IHasDuration, and nested object positions) of a hit object *not* in this set will be computed in next updated. - // Only objects in `AliveObjects` are considered, to prevent a massive recomputation when scrolling speed or something changes. + /// + /// A set containing all which have an up-to-date layout. + /// private readonly HashSet layoutComputed = new HashSet(); [Resolved] @@ -223,8 +226,7 @@ namespace osu.Game.Rulesets.UI.Scrolling toComputeLifetime.Clear(); - // An assumption is that this update won't affect lifetime, - // but this is satisfied in practice because otherwise the hit object won't be aligned to its `StartTime`. + // only AliveObjects need to be considered for layout (reduces overhead in the case of scroll speed changes). foreach (var obj in AliveObjects) { if (layoutComputed.Contains(obj)) From e14db45374a2a60424aad8586edd1851cb0a9040 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 16:09:38 +0900 Subject: [PATCH 269/394] Reorder settings to (probably) feel more correct --- osu.Game/Overlays/SettingsOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index f05d82cb6c..31d188b545 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -23,11 +23,11 @@ namespace osu.Game.Overlays { new GeneralSection(), new GraphicsSection(), + new AudioSection(), + new InputSection(createSubPanel(new KeyBindingPanel())), new UserInterfaceSection(), new GameplaySection(), - new AudioSection(), new SkinSection(), - new InputSection(createSubPanel(new KeyBindingPanel())), new OnlineSection(), new MaintenanceSection(), new DebugSection(), From 55c8aa5d5f7a3ccd83dcc6f0cf574f82f0f0e6e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 16:14:15 +0900 Subject: [PATCH 270/394] Move menu cursor size to UI section --- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 15 +++++++++++++++ .../Overlays/Settings/Sections/SkinSection.cs | 11 ----------- .../Sections/UserInterface/GeneralSettings.cs | 6 ++++++ 3 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Overlays/Settings/Sections/SizeSlider.cs diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs new file mode 100644 index 0000000000..101d8f43f7 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings.Sections +{ + /// + /// A slider intended to show a "size" multiplier number, where 1x is 1.0. + /// + internal class SizeSlider : OsuSliderBar + { + public override string TooltipText => Current.Value.ToString(@"0.##x"); + } +} diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 3e7068f1ff..5898482e4a 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -54,12 +54,6 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown = new SkinSettingsDropdown(), new ExportSkinButton(), new SettingsSlider - { - LabelText = "Menu cursor size", - Current = config.GetBindable(OsuSetting.MenuCursorSize), - KeyboardStep = 0.01f - }, - new SettingsSlider { LabelText = "Gameplay cursor size", Current = config.GetBindable(OsuSetting.GameplayCursorSize), @@ -136,11 +130,6 @@ namespace osu.Game.Overlays.Settings.Sections Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != item.ID).ToArray()); } - private class SizeSlider : OsuSliderBar - { - public override string TooltipText => Current.Value.ToString(@"0.##x"); - } - private class SkinSettingsDropdown : SettingsDropdown { protected override OsuDropdown CreateDropdown() => new SkinDropdownControl(); diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index 797e00a147..da50f67d5f 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -21,6 +21,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface LabelText = "Rotate cursor when dragging", Current = config.GetBindable(OsuSetting.CursorRotation) }, + new SettingsSlider + { + LabelText = "Menu cursor size", + Current = config.GetBindable(OsuSetting.MenuCursorSize), + KeyboardStep = 0.01f + }, new SettingsCheckbox { LabelText = "Parallax", From 4e1e45f3e71ed3353489d3f7ec5e64619814e225 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 16:15:35 +0900 Subject: [PATCH 271/394] Move hold-to-confirm back to UI section --- .../Settings/Sections/Gameplay/GeneralSettings.cs | 12 ------------ .../Sections/UserInterface/GeneralSettings.cs | 12 ++++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 53f1a0b4ba..be464fa2b7 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -5,7 +5,6 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Scoring; namespace osu.Game.Overlays.Settings.Sections.Gameplay @@ -64,12 +63,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Always show key overlay", Current = config.GetBindable(OsuSetting.KeyOverlay) }, - new SettingsSlider - { - LabelText = "Hold-to-confirm activation time", - Current = config.GetBindable(OsuSetting.UIHoldActivationDelay), - KeyboardStep = 50 - }, new SettingsCheckbox { LabelText = "Positional hitsounds", @@ -102,10 +95,5 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay }); } } - - private class TimeSlider : OsuSliderBar - { - public override string TooltipText => Current.Value.ToString("N0") + "ms"; - } } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index da50f67d5f..19adfc5dd9 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections.UserInterface { @@ -32,7 +33,18 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface LabelText = "Parallax", Current = config.GetBindable(OsuSetting.MenuParallax) }, + new SettingsSlider + { + LabelText = "Hold-to-confirm activation time", + Current = config.GetBindable(OsuSetting.UIHoldActivationDelay), + KeyboardStep = 50 + }, }; } + + private class TimeSlider : OsuSliderBar + { + public override string TooltipText => Current.Value.ToString("N0") + "ms"; + } } } From a4e061cb11eeaca6644abca2e3bfee55eacd2c96 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 17:18:29 +0900 Subject: [PATCH 272/394] Remove semi-transparent backgrounds from settings and notifications overlays I tried also updating the colours to the "new" versions from designs but they don't match due to colour profile differences (so I'm not yet sure if they are correct or not) and also don't look great without all the UI elements also being updated. --- osu.Game/Overlays/NotificationOverlay.cs | 4 ++-- osu.Game/Overlays/Settings/Sidebar.cs | 3 ++- osu.Game/Overlays/SettingsOverlay.cs | 2 -- osu.Game/Overlays/SettingsPanel.cs | 7 ++++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index b5714fbcae..774b001224 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -13,6 +13,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Threading; +using osu.Game.Graphics; namespace osu.Game.Overlays { @@ -44,8 +45,7 @@ namespace osu.Game.Overlays new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f + Colour = OsuColour.Gray(0.05f), }, new OsuScrollContainer { diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index 031ecaae46..f548f933e2 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; using osuTK.Graphics; @@ -32,7 +33,7 @@ namespace osu.Game.Overlays.Settings { new Box { - Colour = Color4.Black, + Colour = OsuColour.Gray(0.02f), RelativeSizeAxes = Axes.Both, }, new SidebarScrollContainer diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index e1bcdbbaf0..e49885cdf8 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -61,7 +61,6 @@ namespace osu.Game.Overlays switch (state.NewValue) { case Visibility.Visible: - Background.FadeTo(0.9f, 300, Easing.OutQuint); Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint); SectionsContainer.FadeOut(300, Easing.OutQuint); @@ -69,7 +68,6 @@ namespace osu.Game.Overlays break; case Visibility.Hidden: - Background.FadeTo(0.6f, 500, Easing.OutQuint); Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint); SectionsContainer.FadeIn(500, Easing.OutQuint); diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 2948231c4b..fea4b0738d 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; @@ -72,8 +73,8 @@ namespace osu.Game.Overlays Origin = Anchor.TopRight, Scale = new Vector2(2, 1), // over-extend to the left for transitions RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f, + Colour = OsuColour.Gray(0.05f), + Alpha = 1, }, SectionsContainer = new SettingsSectionsContainer { @@ -214,7 +215,7 @@ namespace osu.Game.Overlays base.UpdateAfterChildren(); // no null check because the usage of this class is strict - HeaderBackground.Alpha = -ExpandableHeader.Y / ExpandableHeader.LayoutSize.Y * 0.5f; + HeaderBackground.Alpha = -ExpandableHeader.Y / ExpandableHeader.LayoutSize.Y * 1; } } } From 7ac2fba1273e8d6d495e76ba9f6e4c3332724c68 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Nov 2020 17:44:58 +0900 Subject: [PATCH 273/394] More reordering of public vs private methods --- .../Scrolling/ScrollingHitObjectContainer.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 6a77597916..3a5e3c098f 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -150,16 +150,9 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - /// - /// Make this lifetime and layout computed in next update. - /// - private void invalidateHitObject(DrawableHitObject hitObject) - { - // Lifetime computation is delayed until next update because - // when the hit object is not pooled this container is not loaded here and `scrollLength` cannot be computed. - toComputeLifetime.Add(hitObject); - layoutComputed.Remove(hitObject); - } + protected override void OnAdd(DrawableHitObject drawableHitObject) => onAddRecursive(drawableHitObject); + + protected override void OnRemove(DrawableHitObject drawableHitObject) => onRemoveRecursive(drawableHitObject); private void onAddRecursive(DrawableHitObject hitObject) { @@ -171,8 +164,6 @@ namespace osu.Game.Rulesets.UI.Scrolling onAddRecursive(nested); } - protected override void OnAdd(DrawableHitObject drawableHitObject) => onAddRecursive(drawableHitObject); - private void onRemoveRecursive(DrawableHitObject hitObject) { toComputeLifetime.Remove(hitObject); @@ -184,7 +175,16 @@ namespace osu.Game.Rulesets.UI.Scrolling onRemoveRecursive(nested); } - protected override void OnRemove(DrawableHitObject drawableHitObject) => onRemoveRecursive(drawableHitObject); + /// + /// Make this lifetime and layout computed in next update. + /// + private void invalidateHitObject(DrawableHitObject hitObject) + { + // Lifetime computation is delayed until next update because + // when the hit object is not pooled this container is not loaded here and `scrollLength` cannot be computed. + toComputeLifetime.Add(hitObject); + layoutComputed.Remove(hitObject); + } private float scrollLength; From bf2c6dc241d561c545dae3dbff0aa35cd8e306ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 17:49:55 +0900 Subject: [PATCH 274/394] Remove unused usings rider couldn't see --- osu.Game/Overlays/NotificationOverlay.cs | 1 - osu.Game/Overlays/Settings/Sidebar.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 774b001224..d51d964fc4 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -6,7 +6,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Overlays.Notifications; -using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using System; diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index f548f933e2..4ca6e2ec42 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -12,7 +12,6 @@ using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.Settings { From fe48b2279c39dee39de905c63e052bc88c6134d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 17:43:53 +0900 Subject: [PATCH 275/394] Adjust various paddings and spacings in settings to make them easier to visually parse --- osu.Game/Overlays/Settings/SettingsSection.cs | 15 +++++++++------ osu.Game/Overlays/Settings/SettingsSubsection.cs | 4 ++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index 97e4ba9da7..8b4821398f 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Settings public virtual IEnumerable FilterTerms => new[] { Header }; private const int header_size = 26; - private const int header_margin = 25; + private const int margin = 20; private const int border_size = 2; public bool MatchingFilter @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Settings protected SettingsSection() { - Margin = new MarginPadding { Top = 20 }; + Margin = new MarginPadding { Top = margin }; AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; @@ -46,10 +46,9 @@ namespace osu.Game.Overlays.Settings { Margin = new MarginPadding { - Top = header_size + header_margin + Top = header_size }, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 30), AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, }; @@ -70,7 +69,7 @@ namespace osu.Game.Overlays.Settings { Padding = new MarginPadding { - Top = 20 + border_size, + Top = margin + border_size, Bottom = 10, }, RelativeSizeAxes = Axes.X, @@ -82,7 +81,11 @@ namespace osu.Game.Overlays.Settings Font = OsuFont.GetFont(size: header_size), Text = Header, Colour = colours.Yellow, - Margin = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS } + Margin = new MarginPadding + { + Left = SettingsPanel.CONTENT_MARGINS, + Right = SettingsPanel.CONTENT_MARGINS + } }, FlowContent } diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index b096c146a6..1b82d973e9 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Settings FlowContent = new FillFlowContainer { Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), + Spacing = new Vector2(0, 8), RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }; @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.Settings new OsuSpriteText { Text = Header.ToUpperInvariant(), - Margin = new MarginPadding { Bottom = 10, Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }, + Margin = new MarginPadding { Vertical = 30, Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }, Font = OsuFont.GetFont(weight: FontWeight.Bold), }, FlowContent From a3ef858f3a03f7c694348d7674a72d963f3dd162 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 30 Nov 2020 17:56:04 +0900 Subject: [PATCH 276/394] Remove unnecessary multiplication --- osu.Game/Overlays/SettingsPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index fea4b0738d..7a5a586f67 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -215,7 +215,7 @@ namespace osu.Game.Overlays base.UpdateAfterChildren(); // no null check because the usage of this class is strict - HeaderBackground.Alpha = -ExpandableHeader.Y / ExpandableHeader.LayoutSize.Y * 1; + HeaderBackground.Alpha = -ExpandableHeader.Y / ExpandableHeader.LayoutSize.Y; } } } From 965cc1f511568b07231c1b189ebd9066d339568e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 17:57:25 +0900 Subject: [PATCH 277/394] Remove unnecessary usings #2 --- osu.Game/Overlays/Settings/SettingsSection.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index 8b4821398f..4143605c28 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -1,16 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; -using osuTK.Graphics; +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; using osu.Game.Graphics.Sprites; -using System.Collections.Generic; -using System.Linq; +using osuTK.Graphics; namespace osu.Game.Overlays.Settings { From 731e689f2da41dcaa040b5dde209f02b1306754d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 18:07:37 +0900 Subject: [PATCH 278/394] Add summary tags to the doc comments --- osu.Game/Utils/StatelessRNG.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Utils/StatelessRNG.cs b/osu.Game/Utils/StatelessRNG.cs index 8d08b26ca4..d78d55cc09 100644 --- a/osu.Game/Utils/StatelessRNG.cs +++ b/osu.Game/Utils/StatelessRNG.cs @@ -3,7 +3,9 @@ namespace osu.Game.Utils { + /// /// Provides a fast stateless function that can be used in randomly-looking visual elements. + /// public static class StatelessRNG { private static ulong mix(ulong x) @@ -19,7 +21,9 @@ namespace osu.Game.Utils } } + /// /// Compute an integer from given seed and series number. + /// /// /// The seed value of this random number generator. /// @@ -30,7 +34,9 @@ namespace osu.Game.Utils public static ulong Get(int seed, int series = 0) => unchecked(mix(((ulong)(uint)series << 32) | ((uint)seed ^ 0x12345678))); + /// /// Compute a floating point value between 0 and 1 (excluding 1) from given seed and series number. + /// /// /// The seed value of this random number generator. /// From fdef6e479c750b253c619e8c8e202bc7ba392a0a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Nov 2020 18:28:04 +0900 Subject: [PATCH 279/394] Remove 1000ms offset and adjust comment --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 243092d067..975b444699 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -176,8 +176,8 @@ namespace osu.Game.Rulesets.Osu.UI public OsuHitObjectLifetimeEntry(HitObject hitObject) : base(hitObject) { - // Arbitrary lifetime end to prevent past objects in idle states remaining alive in non-frame-stable contexts. - LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss) + 1000; + // Prevent past objects in idles states from remaining alive as their end times are skipped in non-frame-stable contexts. + LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss); } protected override double InitialLifetimeOffset => ((OsuHitObject)HitObject).TimePreempt; From 3ad2eeaff551b279e5db2b33ac0d0a0087544d1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 18:35:30 +0900 Subject: [PATCH 280/394] Fix outdated xmldoc --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 9d7dc7b8f7..c0eb891f5e 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Edit } /// - /// Updates the position of this to a new screen-space position. + /// Updates the time and position of this based on the provided snap information. /// /// The snap result information. public virtual void UpdateTimeAndPosition(SnapResult result) From afb8eb636ddc4cd589755f796f1b8bad9e5adb30 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Nov 2020 18:40:22 +0900 Subject: [PATCH 281/394] Apply simple PR reviews --- osu.Game/Skinning/IPooledSampleProvider.cs | 10 +++++++++- osu.Game/Skinning/PoolableSkinnableSample.cs | 4 ++++ osu.Game/Skinning/SkinnableSound.cs | 6 +++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/IPooledSampleProvider.cs b/osu.Game/Skinning/IPooledSampleProvider.cs index 3dc0b5375d..5dbbadcc8a 100644 --- a/osu.Game/Skinning/IPooledSampleProvider.cs +++ b/osu.Game/Skinning/IPooledSampleProvider.cs @@ -6,8 +6,16 @@ using osu.Game.Audio; namespace osu.Game.Skinning { - public interface IPooledSampleProvider + /// + /// Provides pooled samples to be used by s. + /// + internal interface IPooledSampleProvider { + /// + /// Retrieves a from a pool. + /// + /// The describing the sample to retrieve.. + /// The . [CanBeNull] PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo); } diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index adc58ee94e..7e885b20d1 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -138,6 +138,8 @@ namespace osu.Game.Skinning } } + #region Re-expose AudioContainer + public BindableNumber Volume => sampleContainer.Volume; public BindableNumber Balance => sampleContainer.Balance; @@ -159,5 +161,7 @@ namespace osu.Game.Skinning public IBindable AggregateFrequency => sampleContainer.AggregateFrequency; public IBindable AggregateTempo => sampleContainer.AggregateTempo; + + #endregion } } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 24dddaf758..46c2e4b749 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -40,7 +40,7 @@ namespace osu.Game.Skinning private ISampleStore sampleStore { get; set; } [Resolved(CanBeNull = true)] - private IPooledSampleProvider pooledProvider { get; set; } + private IPooledSampleProvider samplePool { get; set; } /// /// Creates a new . @@ -145,7 +145,7 @@ namespace osu.Game.Skinning foreach (var s in samples) { - var sample = pooledProvider?.GetPooledSample(s) ?? new PoolableSkinnableSample(s); + var sample = samplePool?.GetPooledSample(s) ?? new PoolableSkinnableSample(s); sample.Looping = Looping; sample.Volume.Value = s.Volume / 100.0; @@ -176,7 +176,7 @@ namespace osu.Game.Skinning => SamplesContainer.RemoveAllAdjustments(type); /// - /// Whether any samples currently playing. + /// Whether any samples are currently playing. /// public bool IsPlaying => SamplesContainer.Any(s => s.Playing); From 51bddd4a0ff00fce883583852a2d225d5b41117c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 18:46:28 +0900 Subject: [PATCH 282/394] Rename functions, and add NextInt. --- osu.Game/Utils/StatelessRNG.cs | 42 +++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/osu.Game/Utils/StatelessRNG.cs b/osu.Game/Utils/StatelessRNG.cs index d78d55cc09..d316f718e3 100644 --- a/osu.Game/Utils/StatelessRNG.cs +++ b/osu.Game/Utils/StatelessRNG.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; + namespace osu.Game.Utils { /// @@ -22,7 +24,7 @@ namespace osu.Game.Utils } /// - /// Compute an integer from given seed and series number. + /// Generate a random 64-bit unsigned integer from given seed. /// /// /// The seed value of this random number generator. @@ -31,11 +33,39 @@ namespace osu.Game.Utils /// The series number. /// Different values are computed for the same seed in different series. /// - public static ulong Get(int seed, int series = 0) => - unchecked(mix(((ulong)(uint)series << 32) | ((uint)seed ^ 0x12345678))); + public static ulong NextUlong(int seed, int series = 0) + { + unchecked + { + // + var combined = ((ulong)(uint)series << 32) | (uint)seed; + // The xor operation is to not map (0, 0) to 0. + return mix(combined ^ 0x12345678); + } + } /// - /// Compute a floating point value between 0 and 1 (excluding 1) from given seed and series number. + /// Generate a random integer in range [0, maxValue) from given seed. + /// + /// + /// The number of possible results. + /// + /// + /// The seed value of this random number generator. + /// + /// + /// The series number. + /// Different values are computed for the same seed in different series. + /// + public static int NextInt(int maxValue, int seed, int series = 0) + { + if (maxValue <= 0) throw new ArgumentOutOfRangeException(nameof(maxValue)); + + return (int)(NextUlong(seed, series) % (ulong)maxValue); + } + + /// + /// Compute a random floating point value between 0 and 1 (excluding 1) from given seed and series number. /// /// /// The seed value of this random number generator. @@ -44,7 +74,7 @@ namespace osu.Game.Utils /// The series number. /// Different values are computed for the same seed in different series. /// - public static float GetSingle(int seed, int series = 0) => - (float)(Get(seed, series) & ((1 << 24) - 1)) / (1 << 24); // float has 24-bit precision + public static float NextSingle(int seed, int series = 0) => + (float)(NextUlong(seed, series) & ((1 << 24) - 1)) / (1 << 24); // float has 24-bit precision } } From 05aaa377e74078c0e4aa3f591066a0e3644e4e51 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 19:02:49 +0900 Subject: [PATCH 283/394] Don't use CreateDrawableRepresentation in CatcherArea --- .../TestSceneCatcherArea.cs | 1 - osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 1 - osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 17 +++++++++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index e055f08dc2..2d46cbdbbd 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -103,7 +103,6 @@ namespace osu.Game.Rulesets.Catch.Tests { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, - CreateDrawableRepresentation = ((DrawableRuleset)catchRuleset.CreateInstance().CreateDrawableRulesetWith(new CatchBeatmap())).CreateDrawableRepresentation }, }); } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 9df32d8d36..bab8356ac4 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -42,7 +42,6 @@ namespace osu.Game.Rulesets.Catch.UI CatcherArea = new CatcherArea(difficulty) { - CreateDrawableRepresentation = createDrawableRepresentation, ExplodingFruitTarget = explodingFruitContainer, Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index ad79a23279..085af79689 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osuTK; @@ -21,8 +20,6 @@ namespace osu.Game.Rulesets.Catch.UI { public const float CATCHER_SIZE = 106.75f; - public Func> CreateDrawableRepresentation; - public readonly Catcher MovableCatcher; private readonly CatchComboDisplay comboDisplay; @@ -72,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.UI if (result.IsHit && hitObject is DrawablePalpableCatchHitObject fruit) { // create a new (cloned) fruit to stay on the plate. the original is faded out immediately. - var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject); + var caughtFruit = createCaughtFruit(fruit); if (caughtFruit == null) return; @@ -127,5 +124,17 @@ namespace osu.Game.Rulesets.Catch.UI comboDisplay.X = MovableCatcher.X; } + + private DrawableCatchHitObject createCaughtFruit(DrawablePalpableCatchHitObject hitObject) + { + return hitObject.HitObject switch + { + Banana banana => new DrawableBanana(banana), + Fruit fruit => new DrawableFruit(fruit), + TinyDroplet tiny => new DrawableTinyDroplet(tiny), + Droplet droplet => new DrawableDroplet(droplet), + _ => null + }; + } } } From 94fd607a7c548289147008dad8b43a14b1e3a62c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 19:04:09 +0900 Subject: [PATCH 284/394] Use hit object pooling for `Droplet` and `TinyDroplet`. --- .../Objects/Drawables/DrawableDroplet.cs | 8 +++++++- .../Objects/Drawables/DrawableTinyDroplet.cs | 9 ++++++++- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 8 ++++++++ osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 6 ------ 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index 9e76265394..06ecd44488 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Utils; @@ -13,7 +14,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { public override bool StaysOnPlate => false; - public DrawableDroplet(CatchHitObject h) + public DrawableDroplet() + : this(null) + { + } + + public DrawableDroplet([CanBeNull] CatchHitObject h) : base(h) { } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs index 8c4d821b4a..8f5a04dfda 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs @@ -1,13 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; + namespace osu.Game.Rulesets.Catch.Objects.Drawables { public class DrawableTinyDroplet : DrawableDroplet { protected override float ScaleFactor => base.ScaleFactor / 2; - public DrawableTinyDroplet(TinyDroplet h) + public DrawableTinyDroplet() + : this(null) + { + } + + public DrawableTinyDroplet([CanBeNull] TinyDroplet h) : base(h) { } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index bab8356ac4..da909b49c9 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -56,6 +57,13 @@ namespace osu.Game.Rulesets.Catch.UI }; } + [BackgroundDependencyLoader] + private void load() + { + RegisterPool(1); + RegisterPool(1); + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index ebe45aa3ab..8534645947 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -55,12 +55,6 @@ namespace osu.Game.Rulesets.Catch.UI case BananaShower shower: return new DrawableBananaShower(shower, CreateDrawableRepresentation); - - case TinyDroplet tiny: - return new DrawableTinyDroplet(tiny); - - case Droplet droplet: - return new DrawableDroplet(droplet); } return null; From b76ae525b27d5cf0173a3714f170fbf0844b98c0 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 19:07:50 +0900 Subject: [PATCH 285/394] Use hit object pooling for `Fruit` and `Banana`. --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs | 2 -- .../Objects/Drawables/DrawableBanana.cs | 8 +++++++- .../Objects/Drawables/DrawableFruit.cs | 8 +++++++- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 2 ++ osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 6 ------ 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 2d46cbdbbd..c12f38723b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -10,14 +10,12 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; -using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Catch.Tests { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs index efb0958a3a..fb982bbdab 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Utils; @@ -10,7 +11,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { protected override FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => FruitVisualRepresentation.Banana; - public DrawableBanana(Banana h) + public DrawableBanana() + : this(null) + { + } + + public DrawableBanana([CanBeNull] Banana h) : base(h) { } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index 4338d80345..68cb649b66 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Utils; @@ -16,7 +17,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables protected virtual FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => (FruitVisualRepresentation)(indexInBeatmap % 4); - public DrawableFruit(CatchHitObject h) + public DrawableFruit() + : this(null) + { + } + + public DrawableFruit([CanBeNull] Fruit h) : base(h) { } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index da909b49c9..7cddec100f 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -62,6 +62,8 @@ namespace osu.Game.Rulesets.Catch.UI { RegisterPool(1); RegisterPool(1); + RegisterPool(1); + RegisterPool(1); } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 8534645947..ecc37549bf 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -44,12 +44,6 @@ namespace osu.Game.Rulesets.Catch.UI { switch (h) { - case Banana banana: - return new DrawableBanana(banana); - - case Fruit fruit: - return new DrawableFruit(fruit); - case JuiceStream stream: return new DrawableJuiceStream(stream, CreateDrawableRepresentation); From 9611aaf09eb0c9653b2c79d65a062fa2dfe572d4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 19:19:14 +0900 Subject: [PATCH 286/394] Use hit object pooling for `JuiceStream`. - Use `Clear(false)` to not dispose pooled children. - Don't set nested DHO `Origin`. - Simplify the layout (remove custom `Origin`). --- .../TestSceneDrawableHitObjects.cs | 2 +- .../Objects/Drawables/DrawableJuiceStream.cs | 29 +++++-------------- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 1 + .../UI/DrawableCatchRuleset.cs | 3 -- 4 files changed, 9 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index d35f828e28..3e4995482d 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Catch.Tests if (juice.NestedHitObjects.Last() is CatchHitObject tail) tail.LastInCombo = true; // usually the (Catch)BeatmapProcessor would do this for us when necessary - addToPlayfield(new DrawableJuiceStream(juice, drawableRuleset.CreateDrawableRepresentation)); + addToPlayfield(new DrawableJuiceStream(juice)); } private void spawnBananas(bool hit = false) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs index a7a5bfa5ad..a496a35842 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs @@ -1,37 +1,33 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; +using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osuTK; namespace osu.Game.Rulesets.Catch.Objects.Drawables { public class DrawableJuiceStream : DrawableCatchHitObject { - private readonly Func> createDrawableRepresentation; private readonly Container dropletContainer; - public override Vector2 OriginPosition => base.OriginPosition - new Vector2(0, CatchHitObject.OBJECT_RADIUS); + public DrawableJuiceStream() + : this(null) + { + } - public DrawableJuiceStream(JuiceStream s, Func> createDrawableRepresentation = null) + public DrawableJuiceStream([CanBeNull] JuiceStream s) : base(s) { - this.createDrawableRepresentation = createDrawableRepresentation; RelativeSizeAxes = Axes.X; Origin = Anchor.BottomLeft; - X = 0; AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }); } protected override void AddNestedHitObject(DrawableHitObject hitObject) { - hitObject.Origin = Anchor.BottomCentre; - base.AddNestedHitObject(hitObject); dropletContainer.Add(hitObject); } @@ -39,18 +35,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables protected override void ClearNestedHitObjects() { base.ClearNestedHitObjects(); - dropletContainer.Clear(); - } - - protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) - { - switch (hitObject) - { - case CatchHitObject catchObject: - return createDrawableRepresentation?.Invoke(catchObject); - } - - throw new ArgumentException($"{nameof(hitObject)} must be of type {nameof(CatchHitObject)}."); + dropletContainer.Clear(false); } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 7cddec100f..b4d97fdd2b 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -64,6 +64,7 @@ namespace osu.Game.Rulesets.Catch.UI RegisterPool(1); RegisterPool(1); RegisterPool(1); + RegisterPool(1); } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index ecc37549bf..beea7980c9 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -44,9 +44,6 @@ namespace osu.Game.Rulesets.Catch.UI { switch (h) { - case JuiceStream stream: - return new DrawableJuiceStream(stream, CreateDrawableRepresentation); - case BananaShower shower: return new DrawableBananaShower(shower, CreateDrawableRepresentation); } From f5e8d1d14d47934976b930df6cca83831ce07a6b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Nov 2020 19:19:24 +0900 Subject: [PATCH 287/394] Lose old reference immediately on updateSample() --- osu.Game/Skinning/PoolableSkinnableSample.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 7e885b20d1..19b96d6c60 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -83,6 +83,7 @@ namespace osu.Game.Skinning bool wasPlaying = Playing; sampleContainer.Clear(); + Sample = null; var ch = CurrentSkin.GetSample(sampleInfo); From 70628235e36ec7d22e84a885b5ee475cfb88eb6a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 19:22:40 +0900 Subject: [PATCH 288/394] Use hit object pooling for `BananaShower`. --- .../Objects/Drawables/DrawableBananaShower.cs | 26 ++++++------------- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 1 + .../UI/DrawableCatchRuleset.cs | 12 +-------- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs index bf771f690e..9b2f95e221 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs @@ -1,26 +1,27 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; +using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Catch.Objects.Drawables { public class DrawableBananaShower : DrawableCatchHitObject { - private readonly Func> createDrawableRepresentation; private readonly Container bananaContainer; - public DrawableBananaShower(BananaShower s, Func> createDrawableRepresentation = null) + public DrawableBananaShower() + : this(null) + { + } + + public DrawableBananaShower([CanBeNull] BananaShower s) : base(s) { - this.createDrawableRepresentation = createDrawableRepresentation; RelativeSizeAxes = Axes.X; Origin = Anchor.BottomLeft; - X = 0; AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both }); } @@ -34,18 +35,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables protected override void ClearNestedHitObjects() { base.ClearNestedHitObjects(); - bananaContainer.Clear(); - } - - protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) - { - switch (hitObject) - { - case Banana banana: - return createDrawableRepresentation?.Invoke(banana); - } - - return base.CreateNestedHitObject(hitObject); + bananaContainer.Clear(false); } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index b4d97fdd2b..97f33007fe 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Catch.UI RegisterPool(1); RegisterPool(1); RegisterPool(1); + RegisterPool(1); } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index beea7980c9..46733181e3 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -8,7 +8,6 @@ using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; @@ -40,15 +39,6 @@ namespace osu.Game.Rulesets.Catch.UI protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); - public override DrawableHitObject CreateDrawableRepresentation(CatchHitObject h) - { - switch (h) - { - case BananaShower shower: - return new DrawableBananaShower(shower, CreateDrawableRepresentation); - } - - return null; - } + public override DrawableHitObject CreateDrawableRepresentation(CatchHitObject h) => null; } } From f589da43177a1df974bbfc8e59e4596459c22672 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Nov 2020 19:24:38 +0900 Subject: [PATCH 289/394] Fix compilation error --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index e5fc717504..1ba17d9e17 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -104,9 +104,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; - protected override void OnFree(HitObject hitObject) + protected override void OnFree() { - base.OnFree(hitObject); + base.OnFree(); spinningSample.Samples = null; } From c29ad8edf8731bbaf0d3189a2917b00b7f7cde33 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Nov 2020 19:26:25 +0900 Subject: [PATCH 290/394] Better API for dealing with the contained drawable samples --- osu.Game/Skinning/SkinnableSound.cs | 39 +++++++++++-------- .../Drawables/DrawableStoryboardSample.cs | 4 +- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 46c2e4b749..23159e4fe1 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -11,6 +11,7 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Game.Audio; @@ -34,7 +35,13 @@ namespace osu.Game.Skinning /// protected bool PlayWhenZeroVolume => Looping; - protected readonly AudioContainer SamplesContainer; + /// + /// All raw s contained in this . + /// + [NotNull, ItemNotNull] + protected IEnumerable DrawableSamples => samplesContainer.Select(c => c.Sample).Where(s => s != null); + + private readonly AudioContainer samplesContainer; [Resolved] private ISampleStore sampleStore { get; set; } @@ -47,7 +54,7 @@ namespace osu.Game.Skinning /// public SkinnableSound() { - InternalChild = SamplesContainer = new AudioContainer(); + InternalChild = samplesContainer = new AudioContainer(); } /// @@ -105,7 +112,7 @@ namespace osu.Game.Skinning looping = value; - SamplesContainer.ForEach(c => c.Looping = looping); + samplesContainer.ForEach(c => c.Looping = looping); } } @@ -114,7 +121,7 @@ namespace osu.Game.Skinning /// public virtual void Play() { - SamplesContainer.ForEach(c => + samplesContainer.ForEach(c => { if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0) c.Play(); @@ -126,7 +133,7 @@ namespace osu.Game.Skinning /// public virtual void Stop() { - SamplesContainer.ForEach(c => c.Stop()); + samplesContainer.ForEach(c => c.Stop()); } protected override void SkinChanged(ISkinSource skin, bool allowFallback) @@ -140,8 +147,8 @@ namespace osu.Game.Skinning bool wasPlaying = IsPlaying; // Remove all pooled samples (return them to the pool), and dispose the rest. - SamplesContainer.RemoveAll(s => s.IsInPool); - SamplesContainer.Clear(); + samplesContainer.RemoveAll(s => s.IsInPool); + samplesContainer.Clear(); foreach (var s in samples) { @@ -149,7 +156,7 @@ namespace osu.Game.Skinning sample.Looping = Looping; sample.Volume.Value = s.Volume / 100.0; - SamplesContainer.Add(sample); + samplesContainer.Add(sample); } if (wasPlaying) @@ -158,27 +165,27 @@ namespace osu.Game.Skinning #region Re-expose AudioContainer - public BindableNumber Volume => SamplesContainer.Volume; + public BindableNumber Volume => samplesContainer.Volume; - public BindableNumber Balance => SamplesContainer.Balance; + public BindableNumber Balance => samplesContainer.Balance; - public BindableNumber Frequency => SamplesContainer.Frequency; + public BindableNumber Frequency => samplesContainer.Frequency; - public BindableNumber Tempo => SamplesContainer.Tempo; + public BindableNumber Tempo => samplesContainer.Tempo; public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => SamplesContainer.AddAdjustment(type, adjustBindable); + => samplesContainer.AddAdjustment(type, adjustBindable); public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => SamplesContainer.RemoveAdjustment(type, adjustBindable); + => samplesContainer.RemoveAdjustment(type, adjustBindable); public void RemoveAllAdjustments(AdjustableProperty type) - => SamplesContainer.RemoveAllAdjustments(type); + => samplesContainer.RemoveAllAdjustments(type); /// /// Whether any samples are currently playing. /// - public bool IsPlaying => SamplesContainer.Any(s => s.Playing); + public bool IsPlaying => samplesContainer.Any(s => s.Playing); #endregion } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 904af730de..218f051bf0 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -37,8 +37,8 @@ namespace osu.Game.Storyboards.Drawables foreach (var mod in mods.Value.OfType()) { - foreach (var sample in SamplesContainer) - mod.ApplyToSample(sample.Sample); + foreach (var sample in DrawableSamples) + mod.ApplyToSample(sample); } } From a852a27dfb04540e3c43163684cb296d46424179 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 19:36:30 +0900 Subject: [PATCH 291/394] Fix current beatmap temporarily becoming empty during ruleset change When changing the ruleset at song select, there was a scenario where it would be set to default (empty) for one debounce length where this was not actually required. This occurs when the currently selected beatmap set has other difficulties which match the target ruleset, specifically. --- osu.Game/Screens/Select/SongSelect.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b55c0694ef..f32011a27a 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -376,7 +376,7 @@ namespace osu.Game.Screens.Select if (selectionChangedDebounce?.Completed == false) { selectionChangedDebounce.RunTask(); - selectionChangedDebounce.Cancel(); // cancel the already scheduled task. + selectionChangedDebounce?.Cancel(); // cancel the already scheduled task. selectionChangedDebounce = null; } @@ -465,19 +465,30 @@ namespace osu.Game.Screens.Select void run() { + // clear pending task immediately to track any potential nested debounce operation. + selectionChangedDebounce = null; + Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ID.ToString() ?? "null"}"); if (transferRulesetValue()) { Mods.Value = Array.Empty(); - // transferRulesetValue() may trigger a refilter. If the current selection does not match the new ruleset, we want to switch away from it. + // transferRulesetValue() may trigger a re-filter. If the current selection does not match the new ruleset, we want to switch away from it. // The default logic on WorkingBeatmap change is to switch to a matching ruleset (see workingBeatmapChanged()), but we don't want that here. // We perform an early selection attempt and clear out the beatmap selection to avoid a second ruleset change (revert). if (beatmap != null && !Carousel.SelectBeatmap(beatmap, false)) beatmap = null; } + if (selectionChangedDebounce != null) + { + // a new nested operation was started; switch to it for further selection. + // this avoids having two separate debounces trigger from the same source. + selectionChangedDebounce.RunTask(); + return; + } + // We may be arriving here due to another component changing the bindable Beatmap. // In these cases, the other component has already loaded the beatmap, so we don't need to do so again. if (!EqualityComparer.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo)) From 4cd234ea05bb47e6600be35d69a637571d70f315 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 19:56:12 +0900 Subject: [PATCH 292/394] Fix null reference of LegacyFruitPiece --- osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index 1494ef3888..b8648f46f0 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -19,7 +19,8 @@ namespace osu.Game.Rulesets.Catch.Skinning { private readonly string lookupName; - private readonly IBindable accentColour = new Bindable(); + private readonly Bindable accentColour = new Bindable(); + private readonly Bindable hyperDash = new Bindable(); private Sprite colouredSprite; public LegacyFruitPiece(string lookupName) @@ -34,6 +35,7 @@ namespace osu.Game.Rulesets.Catch.Skinning var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject; accentColour.BindTo(drawableCatchObject.AccentColour); + hyperDash.BindTo(drawableCatchObject.HyperDash); InternalChildren = new Drawable[] { @@ -51,9 +53,9 @@ namespace osu.Game.Rulesets.Catch.Skinning }, }; - if (drawableCatchObject.HitObject.HyperDash) + if (hyperDash.Value) { - var hyperDash = new Sprite + var hyperDashOverlay = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -67,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.Skinning Catcher.DEFAULT_HYPER_DASH_COLOUR, }; - AddInternal(hyperDash); + AddInternal(hyperDashOverlay); } } From b5e43144a9be23ee4f2435981ab7f25ea8fcdef0 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 19:56:32 +0900 Subject: [PATCH 293/394] Add a Player test scene that uses a legacy skin --- .../TestSceneCatchPlayerLegacySkin.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs new file mode 100644 index 0000000000..47c542374a --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.IO.Stores; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestSceneCatchPlayerLegacySkin : PlayerTestScene + { + private ISkinSource legacySkinSource; + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(legacySkinSource); + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuGameBase game) + { + var legacySkin = new DefaultLegacySkin(new NamespacedResourceStore(game.Resources, "Skins/Legacy"), audio); + legacySkinSource = new SkinProvidingContainer(legacySkin); + } + + protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); + + public class SkinProvidingPlayer : TestPlayer + { + private readonly ISkinSource skinSource; + + public SkinProvidingPlayer(ISkinSource skinSource) + { + this.skinSource = skinSource; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(skinSource); + return dependencies; + } + } + } +} From b401259f8432c144e96f030e098491996f827240 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 01:19:36 +0900 Subject: [PATCH 294/394] Add test coverage --- .../SongSelect/TestScenePlaySongSelect.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index aa531ba106..a825e2d6fa 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -643,6 +643,54 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmap == filteredBeatmap); } + [Test] + public void TestChangingRulesetOnMultiRulesetBeatmap() + { + int changeCount = 0; + + AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, false)); + AddStep("bind beatmap changed", () => + { + Beatmap.ValueChanged += onChange; + changeCount = 0; + }); + + changeRuleset(0); + + createSongSelect(); + + AddStep("import multi-ruleset map", () => + { + var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray(); + manager.Import(createTestBeatmapSet(usableRulesets)).Wait(); + }); + + int previousSetID = 0; + + AddUntilStep("wait for selection", () => !Beatmap.IsDefault); + + AddStep("record set ID", () => previousSetID = Beatmap.Value.BeatmapSetInfo.ID); + AddAssert("selection changed once", () => changeCount == 1); + + AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0); + + changeRuleset(3); + + AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3); + + AddUntilStep("selection changed", () => changeCount > 1); + + AddAssert("Selected beatmap still same set", () => Beatmap.Value.BeatmapSetInfo.ID == previousSetID); + AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.ID == 3); + + AddAssert("selection changed only fired twice", () => changeCount == 2); + + AddStep("unbind beatmap changed", () => Beatmap.ValueChanged -= onChange); + AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true)); + + void onChange(ValueChangedEvent valueChangedEvent) => changeCount++; + } + [Test] public void TestDifficultyIconSelectingForDifferentRuleset() { From 07e14b126786d766d2c0cde8f185888c28e06c45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 01:37:53 +0900 Subject: [PATCH 295/394] Disable unnecessary inspection --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index a825e2d6fa..35c6d62cb7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -688,6 +688,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("unbind beatmap changed", () => Beatmap.ValueChanged -= onChange); AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true)); + // ReSharper disable once AccessToModifiedClosure void onChange(ValueChangedEvent valueChangedEvent) => changeCount++; } From 2c57deea2bd2724a0a333146f58c2002c18e0c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Nov 2020 19:43:20 +0100 Subject: [PATCH 296/394] Trim double full-stop in xmldoc --- osu.Game/Skinning/IPooledSampleProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/IPooledSampleProvider.cs b/osu.Game/Skinning/IPooledSampleProvider.cs index 5dbbadcc8a..40193d1a1a 100644 --- a/osu.Game/Skinning/IPooledSampleProvider.cs +++ b/osu.Game/Skinning/IPooledSampleProvider.cs @@ -14,7 +14,7 @@ namespace osu.Game.Skinning /// /// Retrieves a from a pool. /// - /// The describing the sample to retrieve.. + /// The describing the sample to retrieve. /// The . [CanBeNull] PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo); From 588a5c2aff768e7139d522b9e3acfe81d111afcf Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 1 Dec 2020 09:35:28 +0900 Subject: [PATCH 297/394] Remove empty comment --- osu.Game/Utils/StatelessRNG.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Utils/StatelessRNG.cs b/osu.Game/Utils/StatelessRNG.cs index d316f718e3..11d079498a 100644 --- a/osu.Game/Utils/StatelessRNG.cs +++ b/osu.Game/Utils/StatelessRNG.cs @@ -37,7 +37,6 @@ namespace osu.Game.Utils { unchecked { - // var combined = ((ulong)(uint)series << 32) | (uint)seed; // The xor operation is to not map (0, 0) to 0. return mix(combined ^ 0x12345678); From 4f17e3520e3dfef916a194d10fb9c46e4abcf9af Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 1 Dec 2020 09:38:19 +0900 Subject: [PATCH 298/394] Use Cached attribute --- .../TestSceneCatchPlayerLegacySkin.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs index 47c542374a..f9f51ef54a 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -28,19 +28,13 @@ namespace osu.Game.Rulesets.Catch.Tests public class SkinProvidingPlayer : TestPlayer { + [Cached(typeof(ISkinSource))] private readonly ISkinSource skinSource; public SkinProvidingPlayer(ISkinSource skinSource) { this.skinSource = skinSource; } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(skinSource); - return dependencies; - } } } } From 604619ba47c41537cac4e49c9aeb67f2dd3320bb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 1 Dec 2020 09:49:04 +0900 Subject: [PATCH 299/394] Generalize legacy skin player test. --- .../TestSceneCatchPlayerLegacySkin.cs | 28 +------------- .../Tests/Visual/LegacySkinPlayerTestScene.cs | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 27 deletions(-) create mode 100644 osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs index f9f51ef54a..64695153b5 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -2,39 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.IO.Stores; -using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneCatchPlayerLegacySkin : PlayerTestScene + public class TestSceneCatchPlayerLegacySkin : LegacySkinPlayerTestScene { - private ISkinSource legacySkinSource; - - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(legacySkinSource); - - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuGameBase game) - { - var legacySkin = new DefaultLegacySkin(new NamespacedResourceStore(game.Resources, "Skins/Legacy"), audio); - legacySkinSource = new SkinProvidingContainer(legacySkin); - } - protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); - - public class SkinProvidingPlayer : TestPlayer - { - [Cached(typeof(ISkinSource))] - private readonly ISkinSource skinSource; - - public SkinProvidingPlayer(ISkinSource skinSource) - { - this.skinSource = skinSource; - } - } } } diff --git a/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs new file mode 100644 index 0000000000..054f72400e --- /dev/null +++ b/osu.Game/Tests/Visual/LegacySkinPlayerTestScene.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.IO.Stores; +using osu.Game.Rulesets; +using osu.Game.Skinning; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public abstract class LegacySkinPlayerTestScene : PlayerTestScene + { + private ISkinSource legacySkinSource; + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(legacySkinSource); + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuGameBase game) + { + var legacySkin = new DefaultLegacySkin(new NamespacedResourceStore(game.Resources, "Skins/Legacy"), audio); + legacySkinSource = new SkinProvidingContainer(legacySkin); + } + + public class SkinProvidingPlayer : TestPlayer + { + [Cached(typeof(ISkinSource))] + private readonly ISkinSource skinSource; + + public SkinProvidingPlayer(ISkinSource skinSource) + { + this.skinSource = skinSource; + } + } + } +} From d1076778fd3749a25b1d1eb2277255bdaee0aab1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 1 Dec 2020 09:50:52 +0900 Subject: [PATCH 300/394] Convert switch expression to switch statement --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 085af79689..26077aeba4 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -127,14 +127,23 @@ namespace osu.Game.Rulesets.Catch.UI private DrawableCatchHitObject createCaughtFruit(DrawablePalpableCatchHitObject hitObject) { - return hitObject.HitObject switch + switch (hitObject.HitObject) { - Banana banana => new DrawableBanana(banana), - Fruit fruit => new DrawableFruit(fruit), - TinyDroplet tiny => new DrawableTinyDroplet(tiny), - Droplet droplet => new DrawableDroplet(droplet), - _ => null - }; + case Banana banana: + return new DrawableBanana(banana); + + case Fruit fruit: + return new DrawableFruit(fruit); + + case TinyDroplet tiny: + return new DrawableTinyDroplet(tiny); + + case Droplet droplet: + return new DrawableDroplet(droplet); + + default: + return null; + } } } } From 08cb84b325e0d11f7cf97f0cb665dbbf6fa3c5bb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 1 Dec 2020 11:32:20 +0900 Subject: [PATCH 301/394] Pool osu!catch hit explosion --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 22 ++-- osu.Game.Rulesets.Catch/UI/HitExplosion.cs | 116 ++++++++++++--------- 2 files changed, 82 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 0f0b9df76e..ae374a7cde 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -107,6 +108,9 @@ namespace osu.Game.Rulesets.Catch.UI private float hyperDashTargetPosition; private Bindable hitLighting; + private DrawablePool hitExplosionPool; + private Container hitExplosionContainer; + public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null) { this.trailsTarget = trailsTarget; @@ -127,6 +131,7 @@ namespace osu.Game.Rulesets.Catch.UI InternalChildren = new Drawable[] { + hitExplosionPool = new DrawablePool(10), caughtFruitContainer, catcherIdle = new CatcherSprite(CatcherAnimationState.Idle) { @@ -142,7 +147,12 @@ namespace osu.Game.Rulesets.Catch.UI { Anchor = Anchor.TopCentre, Alpha = 0, - } + }, + hitExplosionContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + }, }; trails = new CatcherTrailDisplay(this); @@ -209,11 +219,11 @@ namespace osu.Game.Rulesets.Catch.UI if (hitLighting.Value) { - AddInternal(new HitExplosion(fruit) - { - X = fruit.X, - Scale = new Vector2(fruit.HitObject.Scale) - }); + var hitExplosion = hitExplosionPool.Get(); + hitExplosion.X = fruit.X; + hitExplosion.Scale = new Vector2(fruit.HitObject.Scale); + hitExplosion.ObjectColour = fruit.AccentColour.Value; + hitExplosionContainer.Add(hitExplosion); } } diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs index 04a86f83be..9716c1ed09 100644 --- a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs @@ -5,35 +5,40 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Pooling; using osu.Framework.Utils; -using osu.Game.Rulesets.Catch.Objects.Drawables; using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.UI { - public class HitExplosion : CompositeDrawable + public class HitExplosion : PoolableDrawable { - private readonly CircularContainer largeFaint; + private Color4 objectColour; - public HitExplosion(DrawableCatchHitObject fruit) + public Color4 ObjectColour + { + get => objectColour; + set + { + if (objectColour == value) return; + + objectColour = value; + onColourChanged(); + } + } + + private readonly CircularContainer largeFaint, smallFaint, directionalGrow1, directionalGrow2; + + public HitExplosion() { Size = new Vector2(20); Anchor = Anchor.TopCentre; Origin = Anchor.BottomCentre; - Color4 objectColour = fruit.AccentColour.Value; - // scale roughly in-line with visual appearance of notes - - const float angle_variangle = 15; // should be less than 45 - - const float roundness = 100; - const float initial_height = 10; - var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1); - InternalChildren = new Drawable[] { largeFaint = new CircularContainer @@ -42,33 +47,17 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Masking = true, - // we want our size to be very small so the glow dominates it. - Size = new Vector2(0.8f), Blending = BlendingParameters.Additive, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f), - Roundness = 160, - Radius = 200, - }, }, - new CircularContainer + smallFaint = new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Masking = true, Blending = BlendingParameters.Additive, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1), - Roundness = 20, - Radius = 50, - }, }, - new CircularContainer + directionalGrow1 = new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -76,16 +65,8 @@ namespace osu.Game.Rulesets.Catch.UI Masking = true, Size = new Vector2(0.01f, initial_height), Blending = BlendingParameters.Additive, - Rotation = RNG.NextSingle(-angle_variangle, angle_variangle), - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = colour, - Roundness = roundness, - Radius = 40, - }, }, - new CircularContainer + directionalGrow2 = new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -93,30 +74,65 @@ namespace osu.Game.Rulesets.Catch.UI Masking = true, Size = new Vector2(0.01f, initial_height), Blending = BlendingParameters.Additive, - Rotation = RNG.NextSingle(-angle_variangle, angle_variangle), - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = colour, - Roundness = roundness, - Radius = 40, - }, } }; } - protected override void LoadComplete() + protected override void PrepareForUse() { - base.LoadComplete(); + base.PrepareForUse(); const double duration = 400; + // we want our size to be very small so the glow dominates it. + largeFaint.Size = new Vector2(0.8f); largeFaint .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint) .FadeOut(duration * 2); + const float angle_variangle = 15; // should be less than 45 + directionalGrow1.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle); + directionalGrow2.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle); + this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out); Expire(true); } + + private void onColourChanged() + { + const float roundness = 100; + + largeFaint.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f), + Roundness = 160, + Radius = 200, + }; + + smallFaint.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1), + Roundness = 20, + Radius = 50, + }; + + directionalGrow1.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1), + Roundness = roundness, + Radius = 40, + }; + + directionalGrow2.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1), + Roundness = roundness, + Radius = 40, + }; + } } } From e102f2e8fa390853210038a5b3dd3552997e9cdb Mon Sep 17 00:00:00 2001 From: Ryan Zmuda Date: Mon, 30 Nov 2020 21:38:16 -0500 Subject: [PATCH 302/394] Moved enum to bottom, change defualt bind to Shift-Tab, Fixed Notification --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 8 ++++---- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index a59d69e5b5..f4a4813b94 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -68,7 +68,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Tilde }, GlobalAction.QuickExit), new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), - new KeyBinding(InputKey.I, GlobalAction.ToggleInGameInterface), + new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), new KeyBinding(InputKey.Space, GlobalAction.TogglePauseReplay), new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), @@ -146,9 +146,6 @@ namespace osu.Game.Input.Bindings [Description("Decrease scroll speed")] DecreaseScrollSpeed, - [Description("Toggle in-game interface")] - ToggleInGameInterface, - [Description("Select")] Select, @@ -204,5 +201,8 @@ namespace osu.Game.Input.Bindings [Description("Pause / resume replay")] TogglePauseReplay, + + [Description("Toggle in-game interface")] + ToggleInGameInterface, } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 457706b8f5..96d1e211fd 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -181,7 +181,7 @@ namespace osu.Game.Screens.Play notificationOverlay?.Post(new SimpleNotification { - Text = @"The score overlay is currently disabled. You can toggle this by pressing Shift+Tab." + Text = $"The score overlay is currently disabled. You can toggle this by pressing {config.LookupKeyBindings(GlobalAction.ToggleInGameInterface)}." }); } From 5945c088cbacc0c0240c65d63031562d65126d49 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Dec 2020 12:57:37 +0900 Subject: [PATCH 303/394] A few code standard cleanups --- osu.Game.Rulesets.Catch/UI/HitExplosion.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs index 9716c1ed09..ce337c45b4 100644 --- a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs @@ -28,7 +28,10 @@ namespace osu.Game.Rulesets.Catch.UI } } - private readonly CircularContainer largeFaint, smallFaint, directionalGrow1, directionalGrow2; + private readonly CircularContainer largeFaint; + private readonly CircularContainer smallFaint; + private readonly CircularContainer directionalGlow1; + private readonly CircularContainer directionalGlow2; public HitExplosion() { @@ -57,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.UI Masking = true, Blending = BlendingParameters.Additive, }, - directionalGrow1 = new CircularContainer + directionalGlow1 = new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -66,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.UI Size = new Vector2(0.01f, initial_height), Blending = BlendingParameters.Additive, }, - directionalGrow2 = new CircularContainer + directionalGlow2 = new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -91,8 +94,8 @@ namespace osu.Game.Rulesets.Catch.UI .FadeOut(duration * 2); const float angle_variangle = 15; // should be less than 45 - directionalGrow1.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle); - directionalGrow2.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle); + directionalGlow1.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle); + directionalGlow2.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle); this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out); Expire(true); @@ -118,7 +121,7 @@ namespace osu.Game.Rulesets.Catch.UI Radius = 50, }; - directionalGrow1.EdgeEffect = new EdgeEffectParameters + directionalGlow1.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1), @@ -126,7 +129,7 @@ namespace osu.Game.Rulesets.Catch.UI Radius = 40, }; - directionalGrow2.EdgeEffect = new EdgeEffectParameters + directionalGlow2.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1), From c8c1848bb8f554c022ddfd9b039d9848e1bb0ef9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 13:46:30 +0900 Subject: [PATCH 304/394] Fix slider control point dragging not correctly accounting for drag deadzone --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index c06904c0c2..c302e8fe99 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -143,6 +143,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnClick(ClickEvent e) => RequestSelection != null; + private Vector2 dragStartPosition; + protected override bool OnDragStart(DragStartEvent e) { if (RequestSelection == null) @@ -150,6 +152,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (e.Button == MouseButton.Left) { + dragStartPosition = ControlPoint.Position.Value; changeHandler?.BeginChange(); return true; } @@ -174,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components slider.Path.ControlPoints[i].Position.Value -= movementDelta; } else - ControlPoint.Position.Value += e.Delta; + ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition); } protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange(); From 63ff722963bf9762dc3938228e0832ae0a92ad00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 14:00:54 +0900 Subject: [PATCH 305/394] Fix code formatting --- osu.Game/Screens/Play/HUDOverlay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 96d1e211fd..7ab04cf9f9 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -9,7 +9,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -19,7 +18,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; using osuTK; -using osuTK.Input; namespace osu.Game.Screens.Play { @@ -362,6 +360,7 @@ namespace osu.Game.Screens.Play configVisibilityMode.Value = HUDVisibilityMode.Never; break; } + updateVisibility(); return true; } From a147b7186d28cbf1299a019b3cc37f99714a4b6b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 14:01:34 +0900 Subject: [PATCH 306/394] Remove unnecessary call to updateVisibility --- osu.Game/Screens/Play/HUDOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 7ab04cf9f9..50195d571c 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -361,7 +361,6 @@ namespace osu.Game.Screens.Play break; } - updateVisibility(); return true; } From b256c546198b159aae054f79ba585146fe1b5e7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 14:17:36 +0900 Subject: [PATCH 307/394] Scale slider control point display in line with circle size --- .../Sliders/Components/PathControlPointPiece.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index c06904c0c2..41d4d10568 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private OsuColour colours { get; set; } private IBindable sliderPosition; + private IBindable sliderScale; private IBindable controlPointPosition; public PathControlPointPiece(Slider slider, PathControlPoint controlPoint) @@ -69,13 +70,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(10), + Size = new Vector2(20), }, markerRing = new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(14), + Size = new Vector2(28), Masking = true, BorderThickness = 2, BorderColour = Color4.White, @@ -102,6 +103,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components controlPointPosition = ControlPoint.Position.GetBoundCopy(); controlPointPosition.BindValueChanged(_ => updateMarkerDisplay()); + sliderScale = slider.ScaleBindable.GetBoundCopy(); + sliderScale.BindValueChanged(_ => updateMarkerDisplay()); + IsSelected.BindValueChanged(_ => updateMarkerDisplay()); updateMarkerDisplay(); @@ -194,6 +198,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components colour = colour.Lighten(1); marker.Colour = colour; + marker.Scale = new Vector2(slider.Scale); } } } From a16b265090942f7c5fa06234978005776586ff5b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 1 Dec 2020 14:46:04 +0900 Subject: [PATCH 308/394] Apply suggested styling changes --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 2 +- osu.Game.Rulesets.Catch/UI/HitExplosion.cs | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index ae374a7cde..11b6916a4c 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -219,7 +219,7 @@ namespace osu.Game.Rulesets.Catch.UI if (hitLighting.Value) { - var hitExplosion = hitExplosionPool.Get(); + HitExplosion hitExplosion = hitExplosionPool.Get(); hitExplosion.X = fruit.X; hitExplosion.Scale = new Vector2(fruit.HitObject.Scale); hitExplosion.ObjectColour = fruit.AccentColour.Value; diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs index ce337c45b4..24ca778248 100644 --- a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs @@ -121,15 +121,7 @@ namespace osu.Game.Rulesets.Catch.UI Radius = 50, }; - directionalGlow1.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1), - Roundness = roundness, - Radius = 40, - }; - - directionalGlow2.EdgeEffect = new EdgeEffectParameters + directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1), From e8842eed814cec5c1d889515a2075bbbd7ea23d1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 1 Dec 2020 14:50:42 +0900 Subject: [PATCH 309/394] Use bigger initial capacity for catch hit object pool --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 97f33007fe..820f08d439 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -60,12 +60,12 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load() { - RegisterPool(1); - RegisterPool(1); - RegisterPool(1); - RegisterPool(1); - RegisterPool(1); - RegisterPool(1); + RegisterPool(50); + RegisterPool(50); + RegisterPool(100); + RegisterPool(100); + RegisterPool(10); + RegisterPool(2); } protected override void LoadComplete() From 8d101efb24f53f2f4f0549bc1fe734dee18b4815 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 14:56:14 +0900 Subject: [PATCH 310/394] Remove unused proxy layer --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index dd27ac990e..986deccd51 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -300,8 +300,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables this.FadeOut(fade_out_time, Easing.OutQuint).Expire(); } - public Drawable ProxiedLayer => HeadCircle.ProxiedLayer; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos); private class DefaultSliderBody : PlaySliderBody From d0852d7f4adcd63a16cce7b7a281f69a305b4f28 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 14:56:41 +0900 Subject: [PATCH 311/394] Hide slider body immediately on successful hit when snaking is enabled --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 986deccd51..ec653264d0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -294,6 +294,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { case ArmedState.Hit: Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out); + if (sliderBody?.SnakingOut.Value == true) + Body.FadeOut(); break; } From f8ef822e73960ab5e80e5747f312c459db058d20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 15:21:32 +0900 Subject: [PATCH 312/394] Add short fade to better hide transition on default skin --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index ec653264d0..14bcefde0a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -295,7 +295,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out); if (sliderBody?.SnakingOut.Value == true) - Body.FadeOut(); + Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear. break; } From dd05c56a08d778d051eadcf4eda7f241336d87c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 15:34:19 +0900 Subject: [PATCH 313/394] Fix sliders playing hit animations when completely missed --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index dd27ac990e..b65463f438 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -255,7 +255,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || Time.Current < HitObject.EndTime) return; - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); } public override void PlaySamples() From 5760e1c1fca35e580b459ad6a05431efc50a975f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Dec 2020 15:37:51 +0900 Subject: [PATCH 314/394] Make HitSampleInfo immutable --- .../TestSceneAutoJuiceStream.cs | 2 +- osu.Game.Rulesets.Catch/Objects/Banana.cs | 5 + .../Objects/JuiceStream.cs | 7 +- .../TestSceneSlider.cs | 10 +- .../Objects/Drawables/DrawableSlider.cs | 3 +- .../Objects/Drawables/DrawableSpinner.cs | 3 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 9 +- .../Objects/SpinnerBonusTick.cs | 2 +- .../Objects/Drawables/DrawableHit.cs | 6 +- .../Drawables/DrawableTaikoHitObject.cs | 2 +- .../Editing/LegacyEditorBeatmapPatcherTest.cs | 8 +- osu.Game/Audio/HitSampleInfo.cs | 38 +++++-- .../ControlPoints/SampleControlPoint.cs | 14 +-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 7 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 101 +++++++----------- .../Components/ComposeBlueprintContainer.cs | 4 +- .../Compose/Components/SelectionHandler.cs | 2 +- osu.Game/Utils/Optional.cs | 45 ++++++++ 19 files changed, 144 insertions(+), 126 deletions(-) create mode 100644 osu.Game/Utils/Optional.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index 3c636a5b97..0d57fb7029 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Tests NewCombo = i % 8 == 0, Samples = new List(new[] { - new HitSampleInfo { Bank = "normal", Name = "hitnormal", Volume = 100 } + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal") }) }); } diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index ccb1fff15b..822db890e2 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -55,6 +55,11 @@ namespace osu.Game.Rulesets.Catch.Objects private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" }; public override IEnumerable LookupNames => lookupNames; + + public BananaHitSampleInfo() + : base(string.Empty) + { + } } } } diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index e209d012fa..d5819935ad 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -50,12 +50,7 @@ namespace osu.Game.Rulesets.Catch.Objects { base.CreateNestedHitObjects(cancellationToken); - var dropletSamples = Samples.Select(s => new HitSampleInfo - { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - }).ToList(); + var dropletSamples = Samples.Select(s => s.With(@"slidertick")).ToList(); int nodeIndex = 0; SliderEventDescriptor? lastEvent = null; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index c400e2f2ea..d40484f5ed 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -108,8 +108,8 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("change samples", () => slider.HitObject.Samples = new[] { - new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }, - new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE }, + new HitSampleInfo(HitSampleInfo.HIT_CLAP), + new HitSampleInfo(HitSampleInfo.HIT_WHISTLE), }); AddAssert("head samples updated", () => assertSamples(slider.HitObject.HeadCircle)); @@ -136,15 +136,15 @@ namespace osu.Game.Rulesets.Osu.Tests slider = (DrawableSlider)createSlider(repeats: 1); for (int i = 0; i < 2; i++) - slider.HitObject.NodeSamples.Add(new List { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } }); + slider.HitObject.NodeSamples.Add(new List { new HitSampleInfo(HitSampleInfo.HIT_FINISH) }); Add(slider); }); AddStep("change samples", () => slider.HitObject.Samples = new[] { - new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }, - new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE }, + new HitSampleInfo(HitSampleInfo.HIT_CLAP), + new HitSampleInfo(HitSampleInfo.HIT_WHISTLE), }); AddAssert("head samples not updated", () => assertSamples(slider.HitObject.HeadCircle)); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index dd27ac990e..3aa0c9ccb5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -115,8 +115,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (firstSample != null) { - var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); - clone.Name = "sliderslide"; + var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderslide"); samplesContainer.Add(slidingSample = new PausableSkinnableSound(clone) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 2a14a7c975..5a11265a47 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -110,8 +110,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (firstSample != null) { - var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); - clone.Name = "spinnerspin"; + var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("spinnerspin"); samplesContainer.Add(spinningSample = new PausableSkinnableSound(clone) { diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 755ce0866a..1670df24a8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -221,14 +221,7 @@ namespace osu.Game.Rulesets.Osu.Objects var sampleList = new List(); if (firstSample != null) - { - sampleList.Add(new HitSampleInfo - { - Bank = firstSample.Bank, - Volume = firstSample.Volume, - Name = @"slidertick", - }); - } + sampleList.Add(firstSample.With("slidertick")); foreach (var tick in NestedHitObjects.OfType()) tick.Samples = sampleList; diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs index 235dc8710a..2c443cb96b 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Objects { public SpinnerBonusTick() { - Samples.Add(new HitSampleInfo { Name = "spinnerbonus" }); + Samples.Add(new HitSampleInfo("spinnerbonus")); } public override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement(); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 4a3759794b..29a96a7a40 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (isRimType != rimSamples.Any()) { if (isRimType) - HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }); + HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP)); else { foreach (var sample in rimSamples) @@ -125,9 +125,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (s.Name != HitSampleInfo.HIT_FINISH) continue; - var sClone = s.Clone(); - sClone.Name = HitSampleInfo.HIT_WHISTLE; - corrected[i] = sClone; + corrected[i] = s.With(HitSampleInfo.HIT_WHISTLE); } return corrected; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index d8d75a7614..ff5b221273 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (isStrong.Value != strongSamples.Any()) { if (isStrong.Value) - HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH }); + HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); else { foreach (var sample in strongSamples) diff --git a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs index bb56131b04..44a908b756 100644 --- a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Editing HitObjects = { (OsuHitObject)current.HitObjects[0], - new HitCircle { StartTime = 2000, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } } }, + new HitCircle { StartTime = 2000, Samples = { new HitSampleInfo(HitSampleInfo.HIT_FINISH) } }, (OsuHitObject)current.HitObjects[2], } }; @@ -268,12 +268,12 @@ namespace osu.Game.Tests.Editing HitObjects = { (OsuHitObject)current.HitObjects[0], - new HitCircle { StartTime = 1000, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } } }, + new HitCircle { StartTime = 1000, Samples = { new HitSampleInfo(HitSampleInfo.HIT_FINISH) } }, (OsuHitObject)current.HitObjects[2], (OsuHitObject)current.HitObjects[3], - new HitCircle { StartTime = 2250, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE } } }, + new HitCircle { StartTime = 2250, Samples = { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) } }, (OsuHitObject)current.HitObjects[5], - new HitCircle { StartTime = 3000, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP } } }, + new HitCircle { StartTime = 3000, Samples = { new HitSampleInfo(HitSampleInfo.HIT_CLAP) } }, (OsuHitObject)current.HitObjects[7], } }; diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 8efaeb3795..bf4a83e5d7 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.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. +#nullable enable + using System; using System.Collections.Generic; +using osu.Game.Utils; namespace osu.Game.Audio { @@ -22,25 +25,33 @@ namespace osu.Game.Audio /// public static IEnumerable AllAdditions => new[] { HIT_WHISTLE, HIT_CLAP, HIT_FINISH }; - /// - /// The bank to load the sample from. - /// - public string Bank; - /// /// The name of the sample to load. /// - public string Name; + public readonly string Name; + + /// + /// The bank to load the sample from. + /// + public readonly string? Bank; /// /// An optional suffix to provide priority lookup. Falls back to non-suffixed . /// - public string Suffix; + public readonly string? Suffix; /// /// The sample volume. /// - public int Volume { get; set; } + public int Volume { get; } + + public HitSampleInfo(string name, string? bank = null, string? suffix = null, int volume = 100) + { + Name = name; + Bank = bank; + Suffix = suffix; + Volume = volume; + } /// /// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first). @@ -56,6 +67,15 @@ namespace osu.Game.Audio } } - public HitSampleInfo Clone() => (HitSampleInfo)MemberwiseClone(); + /// + /// Creates a new with overridden values. + /// + /// An optional new sample name. + /// An optional new sample bank. + /// An optional new lookup suffix. + /// An optional new volume. + /// The new . + public virtual HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) + => new HitSampleInfo(name.GetOr(Name), bank.GetOr(Bank), suffix.GetOr(Suffix), volume.GetOr(Volume)); } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index f57ecfb9e3..8064da1543 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -58,12 +58,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The name of the same. /// A populated . - public HitSampleInfo GetSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL) => new HitSampleInfo - { - Bank = SampleBank, - Name = sampleName, - Volume = SampleVolume, - }; + public HitSampleInfo GetSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL) => new HitSampleInfo(sampleName, SampleBank, volume: SampleVolume); /// /// Applies and to a if necessary, returning the modified . @@ -71,12 +66,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The . This will not be modified. /// The modified . This does not share a reference with . public virtual HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo) - { - var newSampleInfo = hitSampleInfo.Clone(); - newSampleInfo.Bank = hitSampleInfo.Bank ?? SampleBank; - newSampleInfo.Volume = hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume; - return newSampleInfo; - } + => hitSampleInfo.With(bank: hitSampleInfo.Bank ?? SampleBank, volume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume); public override bool IsRedundant(ControlPoint existing) => existing is SampleControlPoint existingSample diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7ddb0b4caa..df940e8c8e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -192,7 +192,7 @@ namespace osu.Game.Beatmaps.Formats var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) - HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo()); + HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty)); // Convert effect flags to the legacy format LegacyEffectFlags effectFlags = LegacyEffectFlags.None; diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index de4dc8cdc8..9f16180e77 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -182,11 +182,8 @@ namespace osu.Game.Beatmaps.Formats { var baseInfo = base.ApplyTo(hitSampleInfo); - if (baseInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy - && legacy.CustomSampleBank == 0) - { - legacy.CustomSampleBank = CustomSampleBank; - } + if (baseInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0) + return legacy.With(customSampleBank: CustomSampleBank); return baseInfo; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 44b22033dc..9b38266400 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -13,6 +13,7 @@ using JetBrains.Annotations; using osu.Framework.Utils; using osu.Game.Beatmaps.Legacy; using osu.Game.Skinning; +using osu.Game.Utils; namespace osu.Game.Rulesets.Objects.Legacy { @@ -427,62 +428,25 @@ namespace osu.Game.Rulesets.Objects.Legacy // Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario if (!string.IsNullOrEmpty(bankInfo.Filename)) { - return new List - { - new FileHitSampleInfo - { - Filename = bankInfo.Filename, - Volume = bankInfo.Volume - } - }; + return new List { new FileHitSampleInfo(bankInfo.Filename, bankInfo.Volume) }; } var soundTypes = new List { - new LegacyHitSampleInfo - { - Bank = bankInfo.Normal, - Name = HitSampleInfo.HIT_NORMAL, - Volume = bankInfo.Volume, - CustomSampleBank = bankInfo.CustomSampleBank, + new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.Normal, bankInfo.Volume, bankInfo.CustomSampleBank, // if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample. // None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds - IsLayered = type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal) - } + type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal)) }; if (type.HasFlag(LegacyHitSoundType.Finish)) - { - soundTypes.Add(new LegacyHitSampleInfo - { - Bank = bankInfo.Add, - Name = HitSampleInfo.HIT_FINISH, - Volume = bankInfo.Volume, - CustomSampleBank = bankInfo.CustomSampleBank - }); - } + soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank)); if (type.HasFlag(LegacyHitSoundType.Whistle)) - { - soundTypes.Add(new LegacyHitSampleInfo - { - Bank = bankInfo.Add, - Name = HitSampleInfo.HIT_WHISTLE, - Volume = bankInfo.Volume, - CustomSampleBank = bankInfo.CustomSampleBank - }); - } + soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank)); if (type.HasFlag(LegacyHitSoundType.Clap)) - { - soundTypes.Add(new LegacyHitSampleInfo - { - Bank = bankInfo.Add, - Name = HitSampleInfo.HIT_CLAP, - Volume = bankInfo.Volume, - CustomSampleBank = bankInfo.CustomSampleBank - }); - } + soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank)); return soundTypes; } @@ -500,21 +464,11 @@ namespace osu.Game.Rulesets.Objects.Legacy public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); } +#nullable enable + public class LegacyHitSampleInfo : HitSampleInfo { - private int customSampleBank; - - public int CustomSampleBank - { - get => customSampleBank; - set - { - customSampleBank = value; - - if (value >= 2) - Suffix = value.ToString(); - } - } + public readonly int CustomSampleBank; /// /// Whether this hit sample is layered. @@ -523,18 +477,33 @@ namespace osu.Game.Rulesets.Objects.Legacy /// Layered hit samples are automatically added in all modes (except osu!mania), but can be disabled /// using the skin config option. /// - public bool IsLayered { get; set; } + public readonly bool IsLayered; + + public LegacyHitSampleInfo([NotNull] string name, string? bank = null, int volume = 100, int customSampleBank = 0, bool isLayered = false) + : base(name, bank, customSampleBank >= 2 ? customSampleBank.ToString() : null, volume) + { + CustomSampleBank = customSampleBank; + IsLayered = isLayered; + } + + public override HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) + => With(name, bank, volume); + + public LegacyHitSampleInfo With(Optional name = default, Optional bank = default, Optional volume = default, Optional customSampleBank = default, + Optional isLayered = default) + => new LegacyHitSampleInfo(name.GetOr(Name), bank.GetOr(Bank), volume.GetOr(Volume), customSampleBank.GetOr(CustomSampleBank), isLayered.GetOr(IsLayered)); } private class FileHitSampleInfo : LegacyHitSampleInfo { - public string Filename; + public readonly string Filename; - public FileHitSampleInfo() - { - // Make sure that the LegacyBeatmapSkin does not fall back to the user skin. + public FileHitSampleInfo(string filename, int volume) + // Force CSS=1 to make sure that the LegacyBeatmapSkin does not fall back to the user skin. // Note that this does not change the lookup names, as they are overridden locally. - CustomSampleBank = 1; + : base(string.Empty, customSampleBank: 1, volume: volume) + { + Filename = filename; } public override IEnumerable LookupNames => new[] @@ -542,6 +511,14 @@ namespace osu.Game.Rulesets.Objects.Legacy Filename, Path.ChangeExtension(Filename, null) }; + + public override HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) + => With(volume: volume); + + public FileHitSampleInfo With(Optional filename = default, Optional volume = default) + => new FileHitSampleInfo(filename.GetOr(Filename), volume.GetOr(Volume)); } + +#nullable disable } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 1893366d90..c09b935f28 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Edit.Compose.Components case TernaryState.True: if (existingSample == null) - samples.Add(new HitSampleInfo { Name = sampleName }); + samples.Add(new HitSampleInfo(sampleName)); break; } } @@ -212,7 +212,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (blueprint != null) { // doing this post-creations as adding the default hit sample should be the case regardless of the ruleset. - blueprint.HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_NORMAL }); + blueprint.HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL)); placementBlueprintContainer.Child = currentPlacement = blueprint; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index adf22a3370..788b485449 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -328,7 +328,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (h.Samples.Any(s => s.Name == sampleName)) continue; - h.Samples.Add(new HitSampleInfo { Name = sampleName }); + h.Samples.Add(new HitSampleInfo(sampleName)); } EditorBeatmap.EndChange(); diff --git a/osu.Game/Utils/Optional.cs b/osu.Game/Utils/Optional.cs new file mode 100644 index 0000000000..9f8a1c2e62 --- /dev/null +++ b/osu.Game/Utils/Optional.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +namespace osu.Game.Utils +{ + /// + /// A wrapper over a value and a boolean denoting whether the value is valid. + /// + /// The type of value stored. + public readonly ref struct Optional + { + /// + /// The stored value. + /// + public readonly T Value; + + /// + /// Whether is valid. + /// + /// + /// If is a reference type, null may be valid for . + /// + public readonly bool HasValue; + + private Optional(T value) + { + Value = value; + HasValue = true; + } + + /// + /// Returns if it's valid, or a given fallback value otherwise. + /// + /// + /// Shortcase for: optional.HasValue ? optional.Value : fallback. + /// + /// The fallback value to return if is false. + /// + public T GetOr(T fallback) => HasValue ? Value : fallback; + + public static implicit operator Optional(T value) => new Optional(value); + } +} From 199710b941a396cfed43b989a127c5a355522dfa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Dec 2020 15:44:16 +0900 Subject: [PATCH 315/394] Implement equality comparers for HitSampleInfo --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 15 ++++++++++++--- osu.Game/Audio/HitSampleInfo.cs | 10 +++++++++- .../Objects/Legacy/ConvertHitObjectParser.cs | 19 +++++++++++++++++-- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index 822db890e2..ec4b67f341 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using osu.Framework.Utils; using osu.Game.Audio; @@ -50,16 +51,24 @@ namespace osu.Game.Rulesets.Catch.Objects } } - private class BananaHitSampleInfo : HitSampleInfo + private class BananaHitSampleInfo : HitSampleInfo, IEquatable { - private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" }; + private static readonly string[] lookup_names = { "metronomelow", "catch-banana" }; - public override IEnumerable LookupNames => lookupNames; + public override IEnumerable LookupNames => lookup_names; public BananaHitSampleInfo() : base(string.Empty) { } + + public bool Equals(BananaHitSampleInfo other) + => other != null; + + public override bool Equals(object obj) + => Equals((BananaHitSampleInfo)obj); + + public override int GetHashCode() => lookup_names.GetHashCode(); } } } diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index bf4a83e5d7..4a0e48749a 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -13,7 +13,7 @@ namespace osu.Game.Audio /// Describes a gameplay hit sample. /// [Serializable] - public class HitSampleInfo : ISampleInfo + public class HitSampleInfo : ISampleInfo, IEquatable { public const string HIT_WHISTLE = @"hitwhistle"; public const string HIT_FINISH = @"hitfinish"; @@ -77,5 +77,13 @@ namespace osu.Game.Audio /// The new . public virtual HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) => new HitSampleInfo(name.GetOr(Name), bank.GetOr(Bank), suffix.GetOr(Suffix), volume.GetOr(Volume)); + + public bool Equals(HitSampleInfo? other) + => other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix; + + public override bool Equals(object? obj) + => Equals((HitSampleInfo?)obj); + + public override int GetHashCode() => HashCode.Combine(Name, Bank, Suffix); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 9b38266400..931bdb3db7 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -466,7 +466,7 @@ namespace osu.Game.Rulesets.Objects.Legacy #nullable enable - public class LegacyHitSampleInfo : HitSampleInfo + public class LegacyHitSampleInfo : HitSampleInfo, IEquatable { public readonly int CustomSampleBank; @@ -492,9 +492,16 @@ namespace osu.Game.Rulesets.Objects.Legacy public LegacyHitSampleInfo With(Optional name = default, Optional bank = default, Optional volume = default, Optional customSampleBank = default, Optional isLayered = default) => new LegacyHitSampleInfo(name.GetOr(Name), bank.GetOr(Bank), volume.GetOr(Volume), customSampleBank.GetOr(CustomSampleBank), isLayered.GetOr(IsLayered)); + + public bool Equals(LegacyHitSampleInfo? other) + => base.Equals(other) && CustomSampleBank == other.CustomSampleBank && IsLayered == other.IsLayered; + + public override bool Equals(object? obj) => Equals((LegacyHitSampleInfo?)obj); + + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank, IsLayered); } - private class FileHitSampleInfo : LegacyHitSampleInfo + private class FileHitSampleInfo : LegacyHitSampleInfo, IEquatable { public readonly string Filename; @@ -517,6 +524,14 @@ namespace osu.Game.Rulesets.Objects.Legacy public FileHitSampleInfo With(Optional filename = default, Optional volume = default) => new FileHitSampleInfo(filename.GetOr(Filename), volume.GetOr(Volume)); + + public bool Equals(FileHitSampleInfo? other) + => base.Equals(other) && Filename == other.Filename; + + public override bool Equals(object? obj) + => Equals((FileHitSampleInfo?)obj); + + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Filename); } #nullable disable From 2b268ee012f5b4ae9d151d3a2b1fb64e07d488b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 16:16:26 +0900 Subject: [PATCH 316/394] Fix editor beat snapping not working correctly when starting with a new beatmap --- osu.Game/Screens/Edit/Editor.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3d5c0ddad4..8f1cd8e28e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -109,6 +109,12 @@ namespace osu.Game.Screens.Edit beatDivisor.Value = Beatmap.Value.BeatmapInfo.BeatDivisor; beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue); + if (Beatmap.Value is DummyWorkingBeatmap) + { + isNewBeatmap = true; + Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value); + } + // Todo: should probably be done at a DrawableRuleset level to share logic with Player. clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; @@ -122,12 +128,6 @@ namespace osu.Game.Screens.Edit // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); - if (Beatmap.Value is DummyWorkingBeatmap) - { - isNewBeatmap = true; - Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value); - } - try { playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); From 190c6ef45e3fd0cee9581596d85d8ea531160d2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 16:44:08 +0900 Subject: [PATCH 317/394] Fix timeline not updating ticks correctly after arbitrary timing control point changes --- .../Timeline/TimelineTickDisplay.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 724256af8b..71268a1e69 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -25,6 +25,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private BindableBeatDivisor beatDivisor { get; set; } + [Resolved] + private IEditorChangeHandler changeHandler { get; set; } + [Resolved] private OsuColour colours { get; set; } @@ -38,7 +41,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load() { - beatDivisor.BindValueChanged(_ => tickCache.Invalidate()); + beatDivisor.BindValueChanged(_ => invalidateTicks()); + + // currently this is the best way to handle any kind of timing changes. + changeHandler.OnStateChange += invalidateTicks; + } + + private void invalidateTicks() + { + tickCache.Invalidate(); } /// @@ -165,5 +176,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return point; } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (changeHandler != null) + changeHandler.OnStateChange -= invalidateTicks; + } } } From 4900589af4d958c88a0df65a94d647bc06222032 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Dec 2020 17:02:45 +0900 Subject: [PATCH 318/394] Remove unused method --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 931bdb3db7..5895059df5 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -520,10 +520,7 @@ namespace osu.Game.Rulesets.Objects.Legacy }; public override HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) - => With(volume: volume); - - public FileHitSampleInfo With(Optional filename = default, Optional volume = default) - => new FileHitSampleInfo(filename.GetOr(Filename), volume.GetOr(Volume)); + => new FileHitSampleInfo(Filename, volume.GetOr(Volume)); public bool Equals(FileHitSampleInfo? other) => base.Equals(other) && Filename == other.Filename; From d5a60ed335c012e6ba7af05c39fd08e65eeb8f20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 17:30:42 +0900 Subject: [PATCH 319/394] 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 0b43fd73f5..aa4d9fa4ee 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e201383d51..88af0e2138 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index e5f7581404..06b0ec8a13 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - + From 285e62be9a86369c6bcea7105387864b9e9e58af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Dec 2020 17:47:07 +0900 Subject: [PATCH 320/394] Bring code in line with SDL2 defaults --- osu.Desktop/OsuGameDesktop.cs | 2 +- osu.Desktop/Program.cs | 4 ++-- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 0feab9a717..c62d2ee99f 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -138,7 +138,7 @@ namespace osu.Desktop break; // SDL2 DesktopWindow - case DesktopWindow desktopWindow: + case SDL2DesktopWindow desktopWindow: desktopWindow.CursorState |= CursorState.Hidden; desktopWindow.SetIconFromStream(iconStream); desktopWindow.Title = Name; diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 285a813d97..6ca7079654 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -22,9 +22,9 @@ namespace osu.Desktop { // Back up the cwd before DesktopGameHost changes it var cwd = Environment.CurrentDirectory; - bool useSdl = args.Contains("--sdl"); + bool useOsuTK = args.Contains("--tk"); - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useSdl: useSdl)) + using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useOsuTK: useOsuTK)) { host.ExceptionThrown += handleException; diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 14b8dbfac0..62dc1dc806 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -208,7 +208,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private IReadOnlyList getResolutions() { var resolutions = new List { new Size(9999, 9999) }; - var currentDisplay = game.Window?.CurrentDisplay.Value; + var currentDisplay = game.Window?.CurrentDisplayBindable.Value; if (currentDisplay != null) { From b780fdbe4c24efce455c6ebc1b0d53dcc3826eb3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Dec 2020 18:08:59 +0900 Subject: [PATCH 321/394] Ignore lookup types for JSON serialisation --- osu.Game/Audio/HitSampleInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 4a0e48749a..27fb5d2f8e 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -56,6 +56,7 @@ namespace osu.Game.Audio /// /// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first). /// + [JsonIgnore] public virtual IEnumerable LookupNames { get From dda4d76d726592aa3fa7c23639611b3e6694b56d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Dec 2020 18:09:21 +0900 Subject: [PATCH 322/394] Fix bad equality comparer implementations --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 2 +- osu.Game/Audio/HitSampleInfo.cs | 2 +- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index ec4b67f341..7734ebed12 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Objects => other != null; public override bool Equals(object obj) - => Equals((BananaHitSampleInfo)obj); + => obj is BananaHitSampleInfo other && Equals(other); public override int GetHashCode() => lookup_names.GetHashCode(); } diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 27fb5d2f8e..9bd34c009b 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -83,7 +83,7 @@ namespace osu.Game.Audio => other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix; public override bool Equals(object? obj) - => Equals((HitSampleInfo?)obj); + => obj is HitSampleInfo other && Equals(other); public override int GetHashCode() => HashCode.Combine(Name, Bank, Suffix); } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 64694fb12e..015d6a3dd6 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -496,7 +496,8 @@ namespace osu.Game.Rulesets.Objects.Legacy public bool Equals(LegacyHitSampleInfo? other) => base.Equals(other) && CustomSampleBank == other.CustomSampleBank && IsLayered == other.IsLayered; - public override bool Equals(object? obj) => Equals((LegacyHitSampleInfo?)obj); + public override bool Equals(object? obj) + => obj is LegacyHitSampleInfo other && Equals(other); public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank, IsLayered); } @@ -529,7 +530,7 @@ namespace osu.Game.Rulesets.Objects.Legacy => base.Equals(other) && Filename == other.Filename; public override bool Equals(object? obj) - => Equals((FileHitSampleInfo?)obj); + => obj is FileHitSampleInfo other && Equals(other); public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Filename); } From 17560aeeea75adb03347d31e02a85f2b930b0954 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Dec 2020 18:09:28 +0900 Subject: [PATCH 323/394] Volume should be 0 by default --- osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs | 2 +- osu.Game/Audio/HitSampleInfo.cs | 3 ++- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index 0d57fb7029..f552c3c27b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Tests NewCombo = i % 8 == 0, Samples = new List(new[] { - new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal") + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal", volume: 100) }) }); } diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 9bd34c009b..58b078db71 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; using osu.Game.Utils; namespace osu.Game.Audio @@ -45,7 +46,7 @@ namespace osu.Game.Audio /// public int Volume { get; } - public HitSampleInfo(string name, string? bank = null, string? suffix = null, int volume = 100) + public HitSampleInfo(string name, string? bank = null, string? suffix = null, int volume = 0) { Name = name; Bank = bank; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 015d6a3dd6..762edf5a13 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -479,7 +479,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// public readonly bool IsLayered; - public LegacyHitSampleInfo(string name, string? bank = null, int volume = 100, int customSampleBank = 0, bool isLayered = false) + public LegacyHitSampleInfo(string name, string? bank = null, int volume = 0, int customSampleBank = 0, bool isLayered = false) : base(name, bank, customSampleBank >= 2 ? customSampleBank.ToString() : null, volume) { CustomSampleBank = customSampleBank; From c61f00525dfe57252b43df938671ab75ba29be96 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Dec 2020 18:08:59 +0900 Subject: [PATCH 324/394] Ignore lookup types for JSON serialisation --- osu.Game/Audio/HitSampleInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 4a0e48749a..27fb5d2f8e 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -56,6 +56,7 @@ namespace osu.Game.Audio /// /// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first). /// + [JsonIgnore] public virtual IEnumerable LookupNames { get From 6b4a6c12c81a8f22a8b1b0cb012b011d1c9f5275 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Dec 2020 18:09:21 +0900 Subject: [PATCH 325/394] Fix bad equality comparer implementations --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 2 +- osu.Game/Audio/HitSampleInfo.cs | 2 +- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index ec4b67f341..7734ebed12 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Objects => other != null; public override bool Equals(object obj) - => Equals((BananaHitSampleInfo)obj); + => obj is BananaHitSampleInfo other && Equals(other); public override int GetHashCode() => lookup_names.GetHashCode(); } diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 27fb5d2f8e..9bd34c009b 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -83,7 +83,7 @@ namespace osu.Game.Audio => other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix; public override bool Equals(object? obj) - => Equals((HitSampleInfo?)obj); + => obj is HitSampleInfo other && Equals(other); public override int GetHashCode() => HashCode.Combine(Name, Bank, Suffix); } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 5895059df5..a7290401bc 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -496,7 +496,8 @@ namespace osu.Game.Rulesets.Objects.Legacy public bool Equals(LegacyHitSampleInfo? other) => base.Equals(other) && CustomSampleBank == other.CustomSampleBank && IsLayered == other.IsLayered; - public override bool Equals(object? obj) => Equals((LegacyHitSampleInfo?)obj); + public override bool Equals(object? obj) + => obj is LegacyHitSampleInfo other && Equals(other); public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank, IsLayered); } @@ -526,7 +527,7 @@ namespace osu.Game.Rulesets.Objects.Legacy => base.Equals(other) && Filename == other.Filename; public override bool Equals(object? obj) - => Equals((FileHitSampleInfo?)obj); + => obj is FileHitSampleInfo other && Equals(other); public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Filename); } From 284040511289654bf38d18567426edc285f393c5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Dec 2020 18:09:28 +0900 Subject: [PATCH 326/394] Volume should be 0 by default --- osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs | 2 +- osu.Game/Audio/HitSampleInfo.cs | 3 ++- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index 0d57fb7029..f552c3c27b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Tests NewCombo = i % 8 == 0, Samples = new List(new[] { - new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal") + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal", volume: 100) }) }); } diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 9bd34c009b..58b078db71 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; using osu.Game.Utils; namespace osu.Game.Audio @@ -45,7 +46,7 @@ namespace osu.Game.Audio /// public int Volume { get; } - public HitSampleInfo(string name, string? bank = null, string? suffix = null, int volume = 100) + public HitSampleInfo(string name, string? bank = null, string? suffix = null, int volume = 0) { Name = name; Bank = bank; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index a7290401bc..2cb72e9e80 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -479,7 +479,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// public readonly bool IsLayered; - public LegacyHitSampleInfo([NotNull] string name, string? bank = null, int volume = 100, int customSampleBank = 0, bool isLayered = false) + public LegacyHitSampleInfo(string name, string? bank = null, int volume = 0, int customSampleBank = 0, bool isLayered = false) : base(name, bank, customSampleBank >= 2 ? customSampleBank.ToString() : null, volume) { CustomSampleBank = customSampleBank; From 598572195c0f04acdc5c6785a88668464107f108 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Dec 2020 19:57:25 +0900 Subject: [PATCH 327/394] Add playlist length to match settings overlay --- .../Match/Components/MatchSettingsOverlay.cs | 21 +++++++++++++++++++ osu.Game/Tests/Beatmaps/TestBeatmap.cs | 1 + 2 files changed, 22 insertions(+) diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs index caefc194b1..8bf66b084c 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -2,7 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Specialized; +using System.Linq; using Humanizer; +using Humanizer.Localisation; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -69,6 +72,7 @@ namespace osu.Game.Screens.Multi.Match.Components private OsuSpriteText typeLabel; private LoadingLayer loadingLayer; private DrawableRoomPlaylist playlist; + private OsuSpriteText playlistLength; [Resolved(CanBeNull = true)] private IRoomManager manager { get; set; } @@ -229,6 +233,15 @@ namespace osu.Game.Screens.Multi.Match.Components playlist = new DrawableRoomPlaylist(true, true) { RelativeSizeAxes = Axes.Both } }, new Drawable[] + { + playlistLength = new OsuSpriteText + { + Margin = new MarginPadding { Vertical = 5 }, + Colour = colours.Yellow, + Font = OsuFont.GetFont(size: 12), + } + }, + new Drawable[] { new PurpleTriangleButton { @@ -243,6 +256,7 @@ namespace osu.Game.Screens.Multi.Match.Components { new Dimension(), new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), } } }, @@ -315,6 +329,7 @@ namespace osu.Game.Screens.Multi.Match.Components Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue, true); playlist.Items.BindTo(Playlist); + Playlist.BindCollectionChanged(onPlaylistChanged, true); } protected override void Update() @@ -324,6 +339,12 @@ namespace osu.Game.Screens.Multi.Match.Components ApplyButton.Enabled.Value = hasValidSettings; } + private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) + { + double totalLength = Playlist.Select(p => p.Beatmap.Value.Length).Sum(); + playlistLength.Text = $"Length: {totalLength.Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2)}"; + } + private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0; private void apply() diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index 87b77f4616..035cb64099 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -31,6 +31,7 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; BeatmapInfo.BeatmapSet.Files = new List(); BeatmapInfo.BeatmapSet.Beatmaps = new List { BeatmapInfo }; + BeatmapInfo.Length = 75000; BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo { Status = BeatmapSetOnlineStatus.Ranked, From b236c75ac829f2cf76dcfd2f41da6c7f22c89f45 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 1 Dec 2020 17:32:24 +0000 Subject: [PATCH 328/394] Bump Microsoft.Win32.Registry from 4.7.0 to 5.0.0 Bumps [Microsoft.Win32.Registry](https://github.com/dotnet/runtime) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/commits) Signed-off-by: dependabot-preview[bot] --- osu.Desktop/osu.Desktop.csproj | 2 +- osu.Game.Tournament/osu.Game.Tournament.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 62e8f7c518..7b80ca64d0 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -28,7 +28,7 @@ - + diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj index 9cce40c9d3..b049542bb0 100644 --- a/osu.Game.Tournament/osu.Game.Tournament.csproj +++ b/osu.Game.Tournament/osu.Game.Tournament.csproj @@ -9,6 +9,6 @@ - + \ No newline at end of file From 94a8784e04e2bd1157e0bb404fbf616148d82672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Dec 2020 20:08:31 +0100 Subject: [PATCH 329/394] Allow editor change handler to be null --- .../Compose/Components/Timeline/TimelineTickDisplay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 71268a1e69..fb11b859a7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private BindableBeatDivisor beatDivisor { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } [Resolved] @@ -43,8 +43,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { beatDivisor.BindValueChanged(_ => invalidateTicks()); - // currently this is the best way to handle any kind of timing changes. - changeHandler.OnStateChange += invalidateTicks; + if (changeHandler != null) + // currently this is the best way to handle any kind of timing changes. + changeHandler.OnStateChange += invalidateTicks; } private void invalidateTicks() From 03f5460dd2e269c28ae116df0ebd7317f7df9137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Dec 2020 21:57:16 +0100 Subject: [PATCH 330/394] Mark OsuModTestScene as abstract --- osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs index 7697f46160..d3cb3bcf59 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs @@ -5,7 +5,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests.Mods { - public class OsuModTestScene : ModTestScene + public abstract class OsuModTestScene : ModTestScene { protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); } From c9429632f484516b874a91215213cb0fca970a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Dec 2020 22:39:04 +0100 Subject: [PATCH 331/394] Resolve new NRE inspections --- osu.Desktop/OsuGameDesktop.cs | 2 +- osu.Game.Tournament/IPC/FileBasedIPC.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index c62d2ee99f..62d8c17058 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -59,7 +59,7 @@ namespace osu.Desktop try { using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) - stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString()?.Split('"')[1].Replace("osu!.exe", ""); + stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", ""); if (checkExists(stableInstallPath)) return stableInstallPath; diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 999ce61ac8..71417d1cc6 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -243,7 +243,7 @@ namespace osu.Game.Tournament.IPC string stableInstallPath; using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) - stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); + stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", ""); if (ipcFileExistsInDirectory(stableInstallPath)) return stableInstallPath; From 477de1bab0483a6b3e22b82a7fc30eaf4c9610aa Mon Sep 17 00:00:00 2001 From: Pennek <20502902+Pennek@users.noreply.github.com> Date: Wed, 2 Dec 2020 01:11:24 +0100 Subject: [PATCH 332/394] change min/max values --- osu.Game/Screens/Edit/Setup/DifficultySection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index aa1d57db31..897ddc6955 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -37,8 +37,8 @@ namespace osu.Game.Screens.Edit.Setup Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, - MinValue = 2, - MaxValue = 7, + MinValue = 0, + MaxValue = 10, Precision = 0.1f, } }, From a9003466e301abe093880aa36ff97e27d0048e9f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Dec 2020 00:56:05 +0000 Subject: [PATCH 333/394] Bump Microsoft.Build.Traversal from 2.2.3 to 3.0.2 Bumps [Microsoft.Build.Traversal](https://github.com/Microsoft/MSBuildSdks) from 2.2.3 to 3.0.2. - [Release notes](https://github.com/Microsoft/MSBuildSdks/releases) - [Changelog](https://github.com/microsoft/MSBuildSdks/blob/master/RELEASE.md) - [Commits](https://github.com/Microsoft/MSBuildSdks/compare/Microsoft.Build.Traversal.2.2.3...Microsoft.Build.Traversal.3.0.2) Signed-off-by: dependabot-preview[bot] --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 10b61047ac..2c93a533e4 100644 --- a/global.json +++ b/global.json @@ -5,6 +5,6 @@ "version": "3.1.100" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "2.2.3" + "Microsoft.Build.Traversal": "3.0.2" } } \ No newline at end of file From aac20eef441091e6f91000b7c76951d71ced3827 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Dec 2020 00:56:05 +0000 Subject: [PATCH 334/394] Bump System.IO.Packaging from 4.7.0 to 5.0.0 Bumps [System.IO.Packaging](https://github.com/dotnet/runtime) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/commits) Signed-off-by: dependabot-preview[bot] --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 7b80ca64d0..47c0d805b5 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,7 +24,7 @@ - + From 64dfa9f9285affbb68d027782ec9cd69dad6068e Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Dec 2020 00:56:06 +0000 Subject: [PATCH 335/394] Bump Dapper from 2.0.35 to 2.0.78 Bumps [Dapper](https://github.com/StackExchange/Dapper) from 2.0.35 to 2.0.78. - [Release notes](https://github.com/StackExchange/Dapper/releases) - [Commits](https://github.com/StackExchange/Dapper/commits) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 88af0e2138..4de711eb8b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,7 +18,7 @@ - + From 8a65328a6d5914abe07de7eddf0e2a7f234a5113 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Dec 2020 00:56:08 +0000 Subject: [PATCH 336/394] Bump DiscordRichPresence from 1.0.150 to 1.0.166 Bumps [DiscordRichPresence](https://github.com/Lachee/discord-rpc-csharp) from 1.0.150 to 1.0.166. - [Release notes](https://github.com/Lachee/discord-rpc-csharp/releases) - [Commits](https://github.com/Lachee/discord-rpc-csharp/commits) Signed-off-by: dependabot-preview[bot] --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 7b80ca64d0..51300dcef8 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -29,7 +29,7 @@ - + From 5772a0811c69d8b232a03d712b839a1dd1e870bf Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Dec 2020 00:56:09 +0000 Subject: [PATCH 337/394] Bump Microsoft.NET.Test.Sdk from 16.7.1 to 16.8.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.7.1 to 16.8.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.7.1...v16.8.0) Signed-off-by: dependabot-preview[bot] --- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index dfe3bf8af4..61ecd79e3d 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 892f27d27f..fa7bfd7169 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,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 3639c3616f..d6a03da807 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 @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index b59f3a4344..a89645d881 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index c692bcd5e4..83d7b4135a 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 5d55196dcf..bc6b994988 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + From 249793d10a3cf8ab5f3ed5242d295097e637aedd Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Dec 2020 00:56:09 +0000 Subject: [PATCH 338/394] Bump Sentry from 2.1.6 to 2.1.8 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 2.1.6 to 2.1.8. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Changelog](https://github.com/getsentry/sentry-dotnet/blob/main/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/2.1.6...2.1.8) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 88af0e2138..3fa703cf8f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -28,7 +28,7 @@ - + From 989ddd40b42a0766c773eba11ad8ec7f67ea2c44 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Dec 2020 00:56:13 +0000 Subject: [PATCH 339/394] Bump System.ComponentModel.Annotations from 4.7.0 to 5.0.0 Bumps [System.ComponentModel.Annotations](https://github.com/dotnet/runtime) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/commits) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 88af0e2138..923f8f7866 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,6 +31,6 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 06b0ec8a13..b32d3f900a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -92,7 +92,7 @@ - + From e19ef9627a1b0f75b68d184ea5fe5b8d0576dc4d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Dec 2020 10:54:26 +0900 Subject: [PATCH 340/394] Fix potentially incorrect override --- .../Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 2cb72e9e80..119c9ccd7c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -486,11 +486,11 @@ namespace osu.Game.Rulesets.Objects.Legacy IsLayered = isLayered; } - public override HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) + public sealed override HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) => With(name, bank, volume); - public LegacyHitSampleInfo With(Optional name = default, Optional bank = default, Optional volume = default, Optional customSampleBank = default, - Optional isLayered = default) + public virtual LegacyHitSampleInfo With(Optional name = default, Optional bank = default, Optional volume = default, Optional customSampleBank = default, + Optional isLayered = default) => new LegacyHitSampleInfo(name.GetOr(Name), bank.GetOr(Bank), volume.GetOr(Volume), customSampleBank.GetOr(CustomSampleBank), isLayered.GetOr(IsLayered)); public bool Equals(LegacyHitSampleInfo? other) @@ -520,7 +520,8 @@ namespace osu.Game.Rulesets.Objects.Legacy Path.ChangeExtension(Filename, null) }; - public override HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) + public sealed override LegacyHitSampleInfo With(Optional name = default, Optional bank = default, Optional volume = default, Optional customSampleBank = default, + Optional isLayered = default) => new FileHitSampleInfo(Filename, volume.GetOr(Volume)); public bool Equals(FileHitSampleInfo? other) From 2150cf1c52336cc624e7bc13fb4eb78ae18d1bd9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Dec 2020 10:55:48 +0900 Subject: [PATCH 341/394] Rename parameters --- osu.Game/Audio/HitSampleInfo.cs | 12 ++++++------ .../Beatmaps/ControlPoints/SampleControlPoint.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 16 ++++++++-------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 58b078db71..3d90dd0189 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -72,13 +72,13 @@ namespace osu.Game.Audio /// /// Creates a new with overridden values. /// - /// An optional new sample name. - /// An optional new sample bank. - /// An optional new lookup suffix. - /// An optional new volume. + /// An optional new sample name. + /// An optional new sample bank. + /// An optional new lookup suffix. + /// An optional new volume. /// The new . - public virtual HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) - => new HitSampleInfo(name.GetOr(Name), bank.GetOr(Bank), suffix.GetOr(Suffix), volume.GetOr(Volume)); + public virtual HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default) + => new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume)); public bool Equals(HitSampleInfo? other) => other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix; diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 8064da1543..fd0b496335 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -66,7 +66,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The . This will not be modified. /// The modified . This does not share a reference with . public virtual HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo) - => hitSampleInfo.With(bank: hitSampleInfo.Bank ?? SampleBank, volume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume); + => hitSampleInfo.With(newBank: hitSampleInfo.Bank ?? SampleBank, newVolume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume); public override bool IsRedundant(ControlPoint existing) => existing is SampleControlPoint existingSample diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 9f16180e77..c9d139bdd0 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -183,7 +183,7 @@ namespace osu.Game.Beatmaps.Formats var baseInfo = base.ApplyTo(hitSampleInfo); if (baseInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0) - return legacy.With(customSampleBank: CustomSampleBank); + return legacy.With(newCustomSampleBank: CustomSampleBank); return baseInfo; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 119c9ccd7c..72025de131 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -486,12 +486,12 @@ namespace osu.Game.Rulesets.Objects.Legacy IsLayered = isLayered; } - public sealed override HitSampleInfo With(Optional name = default, Optional bank = default, Optional suffix = default, Optional volume = default) - => With(name, bank, volume); + public sealed override HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default) + => With(newName, newBank, newVolume); - public virtual LegacyHitSampleInfo With(Optional name = default, Optional bank = default, Optional volume = default, Optional customSampleBank = default, - Optional isLayered = default) - => new LegacyHitSampleInfo(name.GetOr(Name), bank.GetOr(Bank), volume.GetOr(Volume), customSampleBank.GetOr(CustomSampleBank), isLayered.GetOr(IsLayered)); + public virtual LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, Optional newCustomSampleBank = default, + Optional newIsLayered = default) + => 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; @@ -520,9 +520,9 @@ namespace osu.Game.Rulesets.Objects.Legacy Path.ChangeExtension(Filename, null) }; - public sealed override LegacyHitSampleInfo With(Optional name = default, Optional bank = default, Optional volume = default, Optional customSampleBank = default, - Optional isLayered = default) - => new FileHitSampleInfo(Filename, volume.GetOr(Volume)); + public sealed override LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, Optional newCustomSampleBank = default, + Optional newIsLayered = default) + => new FileHitSampleInfo(Filename, newVolume.GetOr(Volume)); public bool Equals(FileHitSampleInfo? other) => base.Equals(other) && Filename == other.Filename; From 2de3e655e07ad95dfdd9b25ee5d0a440567d7cb8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Dec 2020 12:59:45 +0900 Subject: [PATCH 342/394] Rename NextUlong -> NextULong --- osu.Game/Utils/StatelessRNG.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Utils/StatelessRNG.cs b/osu.Game/Utils/StatelessRNG.cs index 11d079498a..118b08fe30 100644 --- a/osu.Game/Utils/StatelessRNG.cs +++ b/osu.Game/Utils/StatelessRNG.cs @@ -33,7 +33,7 @@ namespace osu.Game.Utils /// The series number. /// Different values are computed for the same seed in different series. /// - public static ulong NextUlong(int seed, int series = 0) + public static ulong NextULong(int seed, int series = 0) { unchecked { @@ -60,7 +60,7 @@ namespace osu.Game.Utils { if (maxValue <= 0) throw new ArgumentOutOfRangeException(nameof(maxValue)); - return (int)(NextUlong(seed, series) % (ulong)maxValue); + return (int)(NextULong(seed, series) % (ulong)maxValue); } /// @@ -74,6 +74,6 @@ namespace osu.Game.Utils /// Different values are computed for the same seed in different series. /// public static float NextSingle(int seed, int series = 0) => - (float)(NextUlong(seed, series) & ((1 << 24) - 1)) / (1 << 24); // float has 24-bit precision + (float)(NextULong(seed, series) & ((1 << 24) - 1)) / (1 << 24); // float has 24-bit precision } } From e84dab858991fdf0363a93b9269789df4208ce00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Dec 2020 14:36:52 +0900 Subject: [PATCH 343/394] Move new beatmap construction above beat divisor binding --- osu.Game/Screens/Edit/Editor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8f1cd8e28e..ca7e5fbf20 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -106,15 +106,15 @@ namespace osu.Game.Screens.Edit [BackgroundDependencyLoader] private void load(OsuColour colours, GameHost host, OsuConfigManager config) { - beatDivisor.Value = Beatmap.Value.BeatmapInfo.BeatDivisor; - beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue); - if (Beatmap.Value is DummyWorkingBeatmap) { isNewBeatmap = true; Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value); } + beatDivisor.Value = Beatmap.Value.BeatmapInfo.BeatDivisor; + beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue); + // Todo: should probably be done at a DrawableRuleset level to share logic with Player. clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; From 946613e803f27025103eb8b0232a4562939877b3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Dec 2020 15:22:45 +0900 Subject: [PATCH 344/394] Fix bananas not playing sounds --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index 7734ebed12..a274f25200 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.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 osu.Framework.Utils; @@ -8,6 +10,7 @@ using osu.Game.Audio; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Utils; using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects @@ -53,19 +56,22 @@ namespace osu.Game.Rulesets.Catch.Objects private class BananaHitSampleInfo : HitSampleInfo, IEquatable { - private static readonly string[] lookup_names = { "metronomelow", "catch-banana" }; + private static readonly string[] lookup_names = { "Gameplay/metronomelow", "Gameplay/catch-banana" }; public override IEnumerable LookupNames => lookup_names; - public BananaHitSampleInfo() - : base(string.Empty) + public BananaHitSampleInfo(int volume = 0) + : base(string.Empty, volume: volume) { } - public bool Equals(BananaHitSampleInfo other) + public sealed override HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default) + => new BananaHitSampleInfo(newVolume.GetOr(Volume)); + + public bool Equals(BananaHitSampleInfo? other) => other != null; - public override bool Equals(object obj) + public override bool Equals(object? obj) => obj is BananaHitSampleInfo other && Equals(other); public override int GetHashCode() => lookup_names.GetHashCode(); From 32188418f446f4624b7ecb90e1144bfa57923ae4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Dec 2020 15:28:39 +0900 Subject: [PATCH 345/394] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index aa4d9fa4ee..9d99218f88 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 53b854caa3..4b931726e0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -27,7 +27,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index b32d3f900a..3a47b77820 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From e7c0e9834f41f5f52fb3b331a02c4742c84bf342 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 16:53:01 +0900 Subject: [PATCH 346/394] Introduce RandomSeed in catch DHO --- .../Drawables/DrawableCatchHitObject.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 1faa6a5b0f..eb5f9451b1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -3,10 +3,13 @@ using System; using JetBrains.Annotations; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Utils; namespace osu.Game.Rulesets.Catch.Objects.Drawables { @@ -20,12 +23,32 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH; + /// + /// The seed value used for visual randomness such as fruit rotation. + /// By default, truncated to an integer is used. + /// + public Bindable RandomSeed = new Bindable(); + protected DrawableCatchHitObject([CanBeNull] CatchHitObject hitObject) : base(hitObject) { Anchor = Anchor.BottomLeft; } + [BackgroundDependencyLoader] + private void load() + { + StartTimeBindable.BindValueChanged(change => + { + RandomSeed.Value = (int)change.NewValue; + }, true); + } + + /// + /// Get a random number in range [0,1) based on seed . + /// + public float RandomSingle(int series) => StatelessRNG.NextSingle(RandomSeed.Value, series); + protected override void OnApply() { base.OnApply(); From 8b6161a51c27fce87f51a171a2a6f4c2e402e212 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 16:54:18 +0900 Subject: [PATCH 347/394] Use deterministic randomness in catch hit object --- .../Objects/Drawables/DrawableBanana.cs | 20 ++++++++++++++----- .../Objects/Drawables/DrawableDroplet.cs | 3 +-- .../Objects/Drawables/DrawableFruit.cs | 8 +++++--- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs index fb982bbdab..4e34dd2b90 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs @@ -3,7 +3,6 @@ using JetBrains.Annotations; using osu.Framework.Graphics; -using osu.Framework.Utils; namespace osu.Game.Rulesets.Catch.Objects.Drawables { @@ -21,6 +20,17 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { } + protected override void LoadComplete() + { + base.LoadComplete(); + + RandomSeed.BindValueChanged(_ => + { + UpdateComboColour(); + UpdateInitialTransforms(); + }); + } + protected override void UpdateInitialTransforms() { base.UpdateInitialTransforms(); @@ -28,14 +38,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables const float end_scale = 0.6f; const float random_scale_range = 1.6f; - ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RNG.NextSingle())) + ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3))) .Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt); - ScaleContainer.RotateTo(getRandomAngle()) + ScaleContainer.RotateTo(getRandomAngle(1)) .Then() - .RotateTo(getRandomAngle(), HitObject.TimePreempt); + .RotateTo(getRandomAngle(2), HitObject.TimePreempt); - float getRandomAngle() => 180 * (RNG.NextSingle() * 2 - 1); + float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1); } public override void PlaySamples() diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index 06ecd44488..b8acea625b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -4,7 +4,6 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osu.Game.Skinning; @@ -45,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables base.UpdateInitialTransforms(); // roughly matches osu-stable - float startRotation = RNG.NextSingle() * 20; + float startRotation = RandomSingle(1) * 20; double duration = HitObject.TimePreempt + 2000; ScaleContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration); diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index 68cb649b66..010a3ee08c 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -5,7 +5,6 @@ using System; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osu.Game.Skinning; @@ -30,8 +29,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables [BackgroundDependencyLoader] private void load() { - ScaleContainer.Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; - IndexInBeatmap.BindValueChanged(change => { VisualRepresentation.Value = GetVisualRepresentation(change.NewValue); @@ -39,6 +36,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables VisualRepresentation.BindValueChanged(_ => updatePiece()); HyperDash.BindValueChanged(_ => updatePiece(), true); + + RandomSeed.BindValueChanged(_ => + { + ScaleContainer.Rotation = (RandomSingle(1) - 0.5f) * 40; + }, true); } private void updatePiece() From 8a78c495f22416e533249ae4173bb2e81b6411c4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 16:55:37 +0900 Subject: [PATCH 348/394] Refactor DHO testing logic to the "specimen" class --- .../TestSceneFruitObjects.cs | 34 +++++++++++++------ .../TestSceneFruitVisualChange.cs | 4 +-- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index 160da75aa9..3a651605d3 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -3,11 +3,12 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; -using osuTK; namespace osu.Game.Rulesets.Catch.Tests { @@ -37,39 +38,50 @@ namespace osu.Game.Rulesets.Catch.Tests } private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) => - SetProperties(new DrawableFruit(new Fruit + new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit { IndexInBeatmap = indexInBeatmap, HyperDashBindable = { Value = hyperdash } })); private Drawable createDrawableBanana() => - SetProperties(new DrawableBanana(new Banana())); + new TestDrawableCatchHitObjectSpecimen(new DrawableBanana(new Banana())); private Drawable createDrawableDroplet(bool hyperdash = false) => - SetProperties(new DrawableDroplet(new Droplet + new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet { HyperDashBindable = { Value = hyperdash } })); - private Drawable createDrawableTinyDroplet() => SetProperties(new DrawableTinyDroplet(new TinyDroplet())); + private Drawable createDrawableTinyDroplet() => new TestDrawableCatchHitObjectSpecimen(new DrawableTinyDroplet(new TinyDroplet())); + } - protected virtual DrawableCatchHitObject SetProperties(DrawableCatchHitObject d) + public class TestDrawableCatchHitObjectSpecimen : CompositeDrawable + { + public readonly ManualClock ManualClock; + + public TestDrawableCatchHitObjectSpecimen(DrawableCatchHitObject d) { + AutoSizeAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + ManualClock = new ManualClock(); + Clock = new FramedClock(ManualClock); + var hitObject = d.HitObject; - hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 0 }); - hitObject.StartTime = 1000000000000; + hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); hitObject.Scale = 1.5f; + hitObject.StartTime = 500; d.Anchor = Anchor.Centre; - d.RelativePositionAxes = Axes.None; - d.Position = Vector2.Zero; d.HitObjectApplied += _ => { d.LifetimeStart = double.NegativeInfinity; d.LifetimeEnd = double.PositiveInfinity; }; - return d; + + InternalChild = d; } } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs index 4448e828e7..125e0c674c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs @@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Catch.Tests protected override void LoadComplete() { - AddStep("fruit changes visual and hyper", () => SetContents(() => SetProperties(new DrawableFruit(new Fruit + AddStep("fruit changes visual and hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit { IndexInBeatmapBindable = { BindTarget = indexInBeatmap }, HyperDashBindable = { BindTarget = hyperDash }, })))); - AddStep("droplet changes hyper", () => SetContents(() => SetProperties(new DrawableDroplet(new Droplet + AddStep("droplet changes hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet { HyperDashBindable = { BindTarget = hyperDash }, })))); From beda6961e433b7df28f69b898c975035e8ce24ee Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 16:55:58 +0900 Subject: [PATCH 349/394] Add a test for fruit randomness --- .../TestSceneFruitRandomness.cs | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs new file mode 100644 index 0000000000..449cfac2db --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class TestSceneFruitRandomness : OsuTestScene + { + [Test] + public void TestFruitRandomness() + { + Bindable randomSeed = new Bindable(); + + TestDrawableFruit drawableFruit; + TestDrawableBanana drawableBanana; + + Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit = new TestDrawableFruit(new Fruit()) + { + RandomSeed = { BindTarget = randomSeed } + }) { X = -200 }); + Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana = new TestDrawableBanana(new Banana()) + { + RandomSeed = { BindTarget = randomSeed } + })); + + float fruitRotation = 0; + float bananaRotation = 0; + float bananaScale = 0; + + AddStep("set random seed to 0", () => + { + drawableFruit.HitObject.StartTime = 500; + randomSeed.Value = 0; + + fruitRotation = drawableFruit.InnerRotation; + bananaRotation = drawableBanana.InnerRotation; + bananaScale = drawableBanana.InnerScale; + }); + + AddStep("change random seed", () => + { + randomSeed.Value = 10; + }); + + AddAssert("fruit rotation is changed", () => drawableFruit.InnerRotation != fruitRotation); + AddAssert("banana rotation is changed", () => drawableBanana.InnerRotation != bananaRotation); + AddAssert("banana scale is changed", () => drawableBanana.InnerScale != bananaScale); + + AddStep("reset random seed", () => + { + randomSeed.Value = 0; + }); + + AddAssert("rotation and scale restored", () => + drawableFruit.InnerRotation == fruitRotation && + drawableBanana.InnerRotation == bananaRotation && + drawableBanana.InnerScale == bananaScale); + + AddStep("change start time", () => + { + drawableFruit.HitObject.StartTime = 1000; + }); + + AddAssert("random seed is changed", () => randomSeed.Value == 1000); + + AddSliderStep("random seed", 0, 100, 0, x => randomSeed.Value = x); + } + + private class TestDrawableFruit : DrawableFruit + { + public float InnerRotation => ScaleContainer.Rotation; + + public TestDrawableFruit(Fruit h) + : base(h) + { + } + } + + private class TestDrawableBanana : DrawableBanana + { + public float InnerRotation => ScaleContainer.Rotation; + public float InnerScale => ScaleContainer.Scale.X; + + public TestDrawableBanana(Banana h) + : base(h) + { + } + } + } +} From 08848e49de025022e9ae8ec0338e067987a53b03 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 17:12:30 +0900 Subject: [PATCH 350/394] Set banana combo colour using random seed --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 13 ++++--------- .../Objects/CatchHitObject.cs | 10 ++++++++++ .../Objects/Drawables/DrawableCatchHitObject.cs | 17 ++--------------- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index 7734ebed12..9fd01b3717 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -3,11 +3,11 @@ using System; using System.Collections.Generic; -using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Utils; using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects @@ -28,17 +28,12 @@ namespace osu.Game.Rulesets.Catch.Objects Samples = samples; } - private Color4? colour; - - Color4 IHasComboInformation.GetComboColour(IReadOnlyList comboColours) - { - // override any external colour changes with banananana - return colour ??= getBananaColour(); - } + // override any external colour changes with banananana + Color4 IHasComboInformation.GetComboColour(IReadOnlyList comboColours) => getBananaColour(); private Color4 getBananaColour() { - switch (RNG.Next(0, 3)) + switch (StatelessRNG.NextInt(3, RandomSeed.Value)) { default: return new Color4(255, 240, 0, 255); diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index a74055bff9..b9c5e42777 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -97,6 +97,12 @@ namespace osu.Game.Rulesets.Catch.Objects set => ScaleBindable.Value = value; } + /// + /// The seed value used for visual randomness such as fruit rotation. + /// By default, truncated to an integer is used. + /// + public Bindable RandomSeed = new Bindable(); + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -111,6 +117,10 @@ namespace osu.Game.Rulesets.Catch.Objects protected CatchHitObject() { XBindable.BindValueChanged(x => originalX = x.NewValue - xOffset); + StartTimeBindable.BindValueChanged(change => + { + RandomSeed.Value = (int)change.NewValue; + }, true); } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index eb5f9451b1..510431ff69 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -3,11 +3,9 @@ using System; using JetBrains.Annotations; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Utils; @@ -23,10 +21,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH; - /// - /// The seed value used for visual randomness such as fruit rotation. - /// By default, truncated to an integer is used. - /// public Bindable RandomSeed = new Bindable(); protected DrawableCatchHitObject([CanBeNull] CatchHitObject hitObject) @@ -35,15 +29,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables Anchor = Anchor.BottomLeft; } - [BackgroundDependencyLoader] - private void load() - { - StartTimeBindable.BindValueChanged(change => - { - RandomSeed.Value = (int)change.NewValue; - }, true); - } - /// /// Get a random number in range [0,1) based on seed . /// @@ -54,6 +39,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables base.OnApply(); XBindable.BindTo(HitObject.XBindable); + RandomSeed.BindTo(HitObject.RandomSeed); } protected override void OnFree() @@ -61,6 +47,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables base.OnFree(); XBindable.UnbindFrom(HitObject.XBindable); + RandomSeed.UnbindFrom(HitObject.RandomSeed); } public Func CheckPosition; From ef741a1471170e62edbd34277a17e9966fcf97cf Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 17:16:36 +0900 Subject: [PATCH 351/394] Test banana colour change based on random seed --- osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs index 449cfac2db..244e2f14f4 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Tests.Visual; +using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Tests { @@ -31,6 +32,7 @@ namespace osu.Game.Rulesets.Catch.Tests float fruitRotation = 0; float bananaRotation = 0; float bananaScale = 0; + Color4 bananaColour = new Color4(); AddStep("set random seed to 0", () => { @@ -40,16 +42,19 @@ namespace osu.Game.Rulesets.Catch.Tests fruitRotation = drawableFruit.InnerRotation; bananaRotation = drawableBanana.InnerRotation; bananaScale = drawableBanana.InnerScale; + bananaColour = drawableBanana.AccentColour.Value; }); AddStep("change random seed", () => { + // Use a seed value such that the banana colour is different (2/3 of the seed values are okay). randomSeed.Value = 10; }); AddAssert("fruit rotation is changed", () => drawableFruit.InnerRotation != fruitRotation); AddAssert("banana rotation is changed", () => drawableBanana.InnerRotation != bananaRotation); AddAssert("banana scale is changed", () => drawableBanana.InnerScale != bananaScale); + AddAssert("banana colour is changed", () => drawableBanana.AccentColour.Value != bananaColour); AddStep("reset random seed", () => { @@ -59,7 +64,8 @@ namespace osu.Game.Rulesets.Catch.Tests AddAssert("rotation and scale restored", () => drawableFruit.InnerRotation == fruitRotation && drawableBanana.InnerRotation == bananaRotation && - drawableBanana.InnerScale == bananaScale); + drawableBanana.InnerScale == bananaScale && + drawableBanana.AccentColour.Value == bananaColour); AddStep("change start time", () => { From 1a6b8e022cba52b12289b2b541effaad186a360e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 17:20:52 +0900 Subject: [PATCH 352/394] Fix formatting --- .../TestSceneFruitRandomness.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs index 244e2f14f4..38efcd85b8 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs @@ -17,17 +17,14 @@ namespace osu.Game.Rulesets.Catch.Tests { Bindable randomSeed = new Bindable(); - TestDrawableFruit drawableFruit; - TestDrawableBanana drawableBanana; + var drawableFruit = new TestDrawableFruit(new Fruit()); + var drawableBanana = new TestDrawableBanana(new Banana()); - Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit = new TestDrawableFruit(new Fruit()) - { - RandomSeed = { BindTarget = randomSeed } - }) { X = -200 }); - Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana = new TestDrawableBanana(new Banana()) - { - RandomSeed = { BindTarget = randomSeed } - })); + drawableFruit.RandomSeed.BindTo(randomSeed); + drawableBanana.RandomSeed.BindTo(randomSeed); + + Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 }); + Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana)); float fruitRotation = 0; float bananaRotation = 0; From 5936a8ffb4a1ea87720b7436bfdddb31ce47d49d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 18:06:14 +0900 Subject: [PATCH 353/394] Fix drawables are added multiple times in a test scene --- .../TestSceneFruitRandomness.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs index 38efcd85b8..5f7447323f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs @@ -12,13 +12,14 @@ namespace osu.Game.Rulesets.Catch.Tests { public class TestSceneFruitRandomness : OsuTestScene { - [Test] - public void TestFruitRandomness() - { - Bindable randomSeed = new Bindable(); + private readonly Bindable randomSeed = new Bindable(); + private readonly TestDrawableFruit drawableFruit; + private readonly TestDrawableBanana drawableBanana; - var drawableFruit = new TestDrawableFruit(new Fruit()); - var drawableBanana = new TestDrawableBanana(new Banana()); + public TestSceneFruitRandomness() + { + drawableFruit = new TestDrawableFruit(new Fruit()); + drawableBanana = new TestDrawableBanana(new Banana()); drawableFruit.RandomSeed.BindTo(randomSeed); drawableBanana.RandomSeed.BindTo(randomSeed); @@ -26,6 +27,12 @@ namespace osu.Game.Rulesets.Catch.Tests Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 }); Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana)); + AddSliderStep("random seed", 0, 100, 0, x => randomSeed.Value = x); + } + + [Test] + public void TestFruitRandomness() + { float fruitRotation = 0; float bananaRotation = 0; float bananaScale = 0; @@ -70,8 +77,6 @@ namespace osu.Game.Rulesets.Catch.Tests }); AddAssert("random seed is changed", () => randomSeed.Value == 1000); - - AddSliderStep("random seed", 0, 100, 0, x => randomSeed.Value = x); } private class TestDrawableFruit : DrawableFruit From d5dccbc3d783c5cc5cb7389fcc970a175ea133e6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Dec 2020 19:02:49 +0900 Subject: [PATCH 354/394] Fix spectator not being thread-safe --- .../Spectator/SpectatorStreamingClient.cs | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 9ba81720d8..08b524087a 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -36,6 +36,8 @@ namespace osu.Game.Online.Spectator private readonly List watchingUsers = new List(); + private readonly object userLock = new object(); + public IBindableList PlayingUsers => playingUsers; private readonly BindableList playingUsers = new BindableList(); @@ -144,12 +146,19 @@ namespace osu.Game.Online.Spectator await connection.StartAsync(); Logger.Log("Spectator client connected!", LoggingTarget.Network); + // get all the users that were previously being watched + int[] users; + + lock (userLock) + { + users = watchingUsers.ToArray(); + watchingUsers.Clear(); + } + // success isConnected = true; // resubscribe to watched users - var users = watchingUsers.ToArray(); - watchingUsers.Clear(); foreach (var userId in users) WatchUser(userId); @@ -238,21 +247,29 @@ namespace osu.Game.Online.Spectator public virtual void WatchUser(int userId) { - if (watchingUsers.Contains(userId)) - return; + lock (userLock) + { + if (watchingUsers.Contains(userId)) + return; - watchingUsers.Add(userId); + watchingUsers.Add(userId); - if (!isConnected) return; + if (!isConnected) + return; + } connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); } public void StopWatchingUser(int userId) { - watchingUsers.Remove(userId); + lock (userLock) + { + watchingUsers.Remove(userId); - if (!isConnected) return; + if (!isConnected) + return; + } connection.SendAsync(nameof(ISpectatorServer.EndWatchingUser), userId); } From fdcfa81e4627c129f7bbcfe32280b09556c96914 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 20:53:47 +0900 Subject: [PATCH 355/394] Make RandomSeed a property, not a bindable --- .../TestSceneFruitRandomness.cs | 35 ++++++++----------- osu.Game.Rulesets.Catch/Objects/Banana.cs | 2 +- .../Objects/CatchHitObject.cs | 8 ++--- .../Objects/Drawables/DrawableBanana.cs | 7 ++-- .../Drawables/DrawableCatchHitObject.cs | 6 ++-- .../Objects/Drawables/DrawableFruit.cs | 10 +++--- 6 files changed, 27 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs index 5f7447323f..2ffebb7de1 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Bindables; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Tests.Visual; @@ -12,7 +11,6 @@ namespace osu.Game.Rulesets.Catch.Tests { public class TestSceneFruitRandomness : OsuTestScene { - private readonly Bindable randomSeed = new Bindable(); private readonly TestDrawableFruit drawableFruit; private readonly TestDrawableBanana drawableBanana; @@ -21,27 +19,30 @@ namespace osu.Game.Rulesets.Catch.Tests drawableFruit = new TestDrawableFruit(new Fruit()); drawableBanana = new TestDrawableBanana(new Banana()); - drawableFruit.RandomSeed.BindTo(randomSeed); - drawableBanana.RandomSeed.BindTo(randomSeed); - Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 }); Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana)); - AddSliderStep("random seed", 0, 100, 0, x => randomSeed.Value = x); + AddSliderStep("start time", 500, 600, 0, x => + { + drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = x; + }); } [Test] public void TestFruitRandomness() { + // Use values such that the banana colour changes (2/3 of the integers are okay) + const int initial_start_time = 500; + const int another_start_time = 501; + float fruitRotation = 0; float bananaRotation = 0; float bananaScale = 0; Color4 bananaColour = new Color4(); - AddStep("set random seed to 0", () => + AddStep("Initialize start time", () => { - drawableFruit.HitObject.StartTime = 500; - randomSeed.Value = 0; + drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time; fruitRotation = drawableFruit.InnerRotation; bananaRotation = drawableBanana.InnerRotation; @@ -49,10 +50,9 @@ namespace osu.Game.Rulesets.Catch.Tests bananaColour = drawableBanana.AccentColour.Value; }); - AddStep("change random seed", () => + AddStep("change start time", () => { - // Use a seed value such that the banana colour is different (2/3 of the seed values are okay). - randomSeed.Value = 10; + drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time; }); AddAssert("fruit rotation is changed", () => drawableFruit.InnerRotation != fruitRotation); @@ -60,9 +60,9 @@ namespace osu.Game.Rulesets.Catch.Tests AddAssert("banana scale is changed", () => drawableBanana.InnerScale != bananaScale); AddAssert("banana colour is changed", () => drawableBanana.AccentColour.Value != bananaColour); - AddStep("reset random seed", () => + AddStep("reset start time", () => { - randomSeed.Value = 0; + drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time; }); AddAssert("rotation and scale restored", () => @@ -70,13 +70,6 @@ namespace osu.Game.Rulesets.Catch.Tests drawableBanana.InnerRotation == bananaRotation && drawableBanana.InnerScale == bananaScale && drawableBanana.AccentColour.Value == bananaColour); - - AddStep("change start time", () => - { - drawableFruit.HitObject.StartTime = 1000; - }); - - AddAssert("random seed is changed", () => randomSeed.Value == 1000); } private class TestDrawableFruit : DrawableFruit diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index 9fd01b3717..3a5e08b3c3 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Objects private Color4 getBananaColour() { - switch (StatelessRNG.NextInt(3, RandomSeed.Value)) + switch (StatelessRNG.NextInt(3, RandomSeed)) { default: return new Color4(255, 240, 0, 255); diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index b9c5e42777..b86b3a7496 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -99,9 +99,9 @@ namespace osu.Game.Rulesets.Catch.Objects /// /// The seed value used for visual randomness such as fruit rotation. - /// By default, truncated to an integer is used. + /// The value is truncated to an integer. /// - public Bindable RandomSeed = new Bindable(); + public int RandomSeed => (int)StartTime; protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { @@ -117,10 +117,6 @@ namespace osu.Game.Rulesets.Catch.Objects protected CatchHitObject() { XBindable.BindValueChanged(x => originalX = x.NewValue - xOffset); - StartTimeBindable.BindValueChanged(change => - { - RandomSeed.Value = (int)change.NewValue; - }, true); } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs index 4e34dd2b90..8e9d80106b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs @@ -24,11 +24,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { base.LoadComplete(); - RandomSeed.BindValueChanged(_ => - { - UpdateComboColour(); - UpdateInitialTransforms(); - }); + // start time affects the random seed which is used to determine the banana colour + StartTimeBindable.BindValueChanged(_ => UpdateComboColour()); } protected override void UpdateInitialTransforms() diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 510431ff69..86c1c7d0cd 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH; - public Bindable RandomSeed = new Bindable(); + public int RandomSeed => HitObject?.RandomSeed ?? 0; protected DrawableCatchHitObject([CanBeNull] CatchHitObject hitObject) : base(hitObject) @@ -32,14 +32,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables /// /// Get a random number in range [0,1) based on seed . /// - public float RandomSingle(int series) => StatelessRNG.NextSingle(RandomSeed.Value, series); + public float RandomSingle(int series) => StatelessRNG.NextSingle(RandomSeed, series); protected override void OnApply() { base.OnApply(); XBindable.BindTo(HitObject.XBindable); - RandomSeed.BindTo(HitObject.RandomSeed); } protected override void OnFree() @@ -47,7 +46,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables base.OnFree(); XBindable.UnbindFrom(HitObject.XBindable); - RandomSeed.UnbindFrom(HitObject.RandomSeed); } public Func CheckPosition; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index 010a3ee08c..56b9b56372 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -36,11 +36,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables VisualRepresentation.BindValueChanged(_ => updatePiece()); HyperDash.BindValueChanged(_ => updatePiece(), true); + } - RandomSeed.BindValueChanged(_ => - { - ScaleContainer.Rotation = (RandomSingle(1) - 0.5f) * 40; - }, true); + protected override void UpdateInitialTransforms() + { + base.UpdateInitialTransforms(); + + ScaleContainer.Rotation = (RandomSingle(1) - 0.5f) * 40; } private void updatePiece() From bbde1f6b9c755df552ec02c8194866b51f17dbf2 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 1 Dec 2020 22:26:17 +0100 Subject: [PATCH 356/394] Fix MouseHandler not being ignored when raw input is enabled --- .../Settings/Sections/Input/MouseSettings.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index f0d51a0d37..b9e9de1200 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Input.Handlers.Mouse; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Input; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private readonly BindableBool rawInputToggle = new BindableBool(); private Bindable sensitivityBindable = new BindableDouble(); - private Bindable ignoredInputHandler; + private Bindable ignoredInputHandlers; [BackgroundDependencyLoader] private void load(OsuConfigManager osuConfig, FrameworkConfigManager config) @@ -75,20 +76,21 @@ namespace osu.Game.Overlays.Settings.Sections.Input { // this is temporary until we support per-handler settings. const string raw_mouse_handler = @"OsuTKRawMouseHandler"; - const string standard_mouse_handler = @"OsuTKMouseHandler"; + const string osutk_standard_mouse_handler = @"OsuTKMouseHandler"; + string standardMouseHandlers = $"{osutk_standard_mouse_handler} {nameof(MouseHandler)}"; - ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler; + ignoredInputHandlers.Value = enabled.NewValue ? standardMouseHandlers : raw_mouse_handler; }; - ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers); - ignoredInputHandler.ValueChanged += handler => + ignoredInputHandlers = config.GetBindable(FrameworkSetting.IgnoredInputHandlers); + ignoredInputHandlers.ValueChanged += handler => { bool raw = !handler.NewValue.Contains("Raw"); rawInputToggle.Value = raw; sensitivityBindable.Disabled = !raw; }; - ignoredInputHandler.TriggerChange(); + ignoredInputHandlers.TriggerChange(); } } From a2a10d4e131e3ecbedcec977f86e4ac8a426db79 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Wed, 2 Dec 2020 19:45:59 +0100 Subject: [PATCH 357/394] Don't use nameof(MouseHandler) --- osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index b9e9de1200..b5d7a1ac0e 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -76,10 +76,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input { // this is temporary until we support per-handler settings. const string raw_mouse_handler = @"OsuTKRawMouseHandler"; - const string osutk_standard_mouse_handler = @"OsuTKMouseHandler"; - string standardMouseHandlers = $"{osutk_standard_mouse_handler} {nameof(MouseHandler)}"; + const string standard_mouse_handlers = @"OsuTKMouseHandler MouseHandler"; - ignoredInputHandlers.Value = enabled.NewValue ? standardMouseHandlers : raw_mouse_handler; + ignoredInputHandlers.Value = enabled.NewValue ? standard_mouse_handlers : raw_mouse_handler; }; ignoredInputHandlers = config.GetBindable(FrameworkSetting.IgnoredInputHandlers); From 7fd385efe60cb117f881be9afa7d4377c2f13aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 2 Dec 2020 20:01:56 +0100 Subject: [PATCH 358/394] Remove unused using directive --- osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index b5d7a1ac0e..b54ad9a641 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Framework.Input.Handlers.Mouse; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Input; From 2e8195e059d8cd2f17d5477460797c4279eef86c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 3 Dec 2020 12:13:14 +0900 Subject: [PATCH 359/394] Use transformation to set fruit rotation --- osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index 56b9b56372..ef9df02a68 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -5,6 +5,7 @@ using System; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osu.Game.Skinning; @@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { base.UpdateInitialTransforms(); - ScaleContainer.Rotation = (RandomSingle(1) - 0.5f) * 40; + ScaleContainer.RotateTo((RandomSingle(1) - 0.5f) * 40); } private void updatePiece() From 6c46046c2426ef38fc6589f737e180adecea7829 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 3 Dec 2020 12:32:49 +0900 Subject: [PATCH 360/394] Fix DHO expires while hit sound is playing --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- osu.Game/Skinning/PausableSkinnableSound.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index a922da0aa9..ad96b1caef 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -453,7 +453,7 @@ namespace osu.Game.Rulesets.Objects.Drawables state.Value = newState; if (LifetimeEnd == double.MaxValue && (state.Value != ArmedState.Idle || HitObject.HitWindows == null)) - Expire(); + LifetimeEnd = Math.Max(LatestTransformEndTime, HitStateUpdateTime + (Samples?.Length ?? 0)); // apply any custom state overrides ApplyCustomUpdateState?.Invoke(this, newState); diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index 4f09aec0b6..7056b2b2ad 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Threading; @@ -12,6 +13,8 @@ namespace osu.Game.Skinning { public class PausableSkinnableSound : SkinnableSound { + public double Length => SamplesContainer.Children.Count == 0 ? 0 : SamplesContainer.Children.Max(sample => sample.Length); + protected bool RequestedPlaying { get; private set; } public PausableSkinnableSound(ISampleInfo hitSamples) From 897f593b379b10c2765b54ff22618b821c3ad77e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Dec 2020 13:26:28 +0900 Subject: [PATCH 361/394] Fix beatmap carousel panels getting masked away when out of scroll bounds Regressed in https://github.com/ppy/osu/pull/10973 due to removed masking specification. Closes #11067. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 4ce87927a1..d76f0abb9e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -914,6 +914,9 @@ namespace osu.Game.Screens.Select { // size is determined by the carousel itself, due to not all content necessarily being loaded. ScrollContent.AutoSizeAxes = Axes.None; + + // the scroll container may get pushed off-screen by global screen changes, but we still want panels to display outside of the bounds. + Masking = false; } // ReSharper disable once OptionalParameterHierarchyMismatch 2020.3 EAP4 bug. (https://youtrack.jetbrains.com/issue/RSRP-481535?p=RIDER-51910) From e3bbc2b1284ab2b7bdeff55843146ab86b4321b1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Dec 2020 14:28:37 +0900 Subject: [PATCH 362/394] Rework osu! hidden mod to avoid storing hitobjects --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 134 +++++++++--------- .../Objects/Drawables/DrawableSliderHead.cs | 17 ++- .../Objects/Drawables/DrawableSliderRepeat.cs | 4 + .../Objects/Drawables/DrawableSliderTail.cs | 12 ++ 4 files changed, 97 insertions(+), 70 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 7c1dd46c02..78e759f0e0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -2,9 +2,10 @@ // 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.Graphics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -25,23 +26,19 @@ namespace osu.Game.Rulesets.Osu.Mods protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner); - public override void ApplyToDrawableHitObjects(IEnumerable drawables) + public override void ApplyToBeatmap(IBeatmap beatmap) { - foreach (var d in drawables) - d.HitObjectApplied += applyFadeInAdjustment; + base.ApplyToBeatmap(beatmap); - base.ApplyToDrawableHitObjects(drawables); - } + foreach (var obj in beatmap.HitObjects.OfType()) + applyFadeInAdjustment(obj); - private void applyFadeInAdjustment(DrawableHitObject hitObject) - { - if (!(hitObject is DrawableOsuHitObject d)) - return; - - d.HitObject.TimeFadeIn = d.HitObject.TimePreempt * fade_in_duration_multiplier; - - foreach (var nested in d.NestedHitObjects) - applyFadeInAdjustment(nested); + static void applyFadeInAdjustment(OsuHitObject osuObject) + { + osuObject.TimeFadeIn = osuObject.TimePreempt * fade_in_duration_multiplier; + foreach (var nested in osuObject.NestedHitObjects.OfType()) + applyFadeInAdjustment(nested); + } } protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) @@ -56,37 +53,27 @@ namespace osu.Game.Rulesets.Osu.Mods applyState(hitObject, false); } - private void applyState(DrawableHitObject drawable, bool increaseVisibility) + private void applyState(DrawableHitObject drawableObject, bool increaseVisibility) { - if (!(drawable is DrawableOsuHitObject d)) + if (!(drawableObject is DrawableOsuHitObject drawableOsuObject)) return; - var h = d.HitObject; + OsuHitObject hitObject = drawableOsuObject.HitObject; - var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadeIn; - var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier; + (double startTime, double duration) fadeOut = getFadeOutParameters(drawableOsuObject); - // new duration from completed fade in to end (before fading out) - var longFadeDuration = h.GetEndTime() - fadeOutStartTime; - - switch (drawable) + switch (drawableObject) { - case DrawableSliderTail sliderTail: - // use stored values from head circle to achieve same fade sequence. - var tailFadeOutParameters = getFadeOutParametersFromSliderHead(h); - - using (drawable.BeginAbsoluteSequence(tailFadeOutParameters.startTime, true)) - sliderTail.FadeOut(tailFadeOutParameters.duration); + case DrawableSliderTail _: + using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true)) + drawableObject.FadeOut(fadeOut.duration); break; case DrawableSliderRepeat sliderRepeat: - // use stored values from head circle to achieve same fade sequence. - var repeatFadeOutParameters = getFadeOutParametersFromSliderHead(h); - - using (drawable.BeginAbsoluteSequence(repeatFadeOutParameters.startTime, true)) + using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true)) // only apply to circle piece – reverse arrow is not affected by hidden. - sliderRepeat.CirclePiece.FadeOut(repeatFadeOutParameters.duration); + sliderRepeat.CirclePiece.FadeOut(fadeOut.duration); break; @@ -101,29 +88,23 @@ namespace osu.Game.Rulesets.Osu.Mods else { // we don't want to see the approach circle - using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt, true)) circle.ApproachCircle.Hide(); } - // fade out immediately after fade in. - using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) - fadeTarget.FadeOut(fadeOutDuration); + using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true)) + fadeTarget.FadeOut(fadeOut.duration); break; case DrawableSlider slider: - associateNestedSliderCirclesWithHead(slider.HitObject); - - using (slider.BeginAbsoluteSequence(fadeOutStartTime, true)) - slider.Body.FadeOut(longFadeDuration, Easing.Out); + using (slider.BeginAbsoluteSequence(fadeOut.startTime, true)) + slider.Body.FadeOut(fadeOut.duration, Easing.Out); break; case DrawableSliderTick sliderTick: - // slider ticks fade out over up to one second - var tickFadeOutDuration = Math.Min(sliderTick.HitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); - - using (sliderTick.BeginAbsoluteSequence(sliderTick.HitObject.StartTime - tickFadeOutDuration, true)) - sliderTick.FadeOut(tickFadeOutDuration); + using (sliderTick.BeginAbsoluteSequence(fadeOut.startTime, true)) + sliderTick.FadeOut(fadeOut.duration); break; @@ -131,30 +112,55 @@ namespace osu.Game.Rulesets.Osu.Mods // hide elements we don't care about. // todo: hide background - using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true)) - spinner.FadeOut(fadeOutDuration); + using (spinner.BeginAbsoluteSequence(fadeOut.startTime, true)) + spinner.FadeOut(fadeOut.duration); break; } } - private readonly Dictionary correspondingSliderHeadForObject = new Dictionary(); - - private void associateNestedSliderCirclesWithHead(Slider slider) + private (double startTime, double duration) getFadeOutParameters(DrawableOsuHitObject drawableObject) { - var sliderHead = slider.NestedHitObjects.Single(obj => obj is SliderHeadCircle); - - foreach (var nested in slider.NestedHitObjects) + switch (drawableObject) { - if ((nested is SliderRepeat || nested is SliderEndCircle) && !correspondingSliderHeadForObject.ContainsKey(nested)) - correspondingSliderHeadForObject[nested] = (SliderHeadCircle)sliderHead; - } - } + case DrawableSliderTail tail: + // Use the same fade sequence as the slider head. + Debug.Assert(tail.Slider != null); + return getParameters(tail.Slider.HeadCircle); - private (double startTime, double duration) getFadeOutParametersFromSliderHead(OsuHitObject h) - { - var sliderHead = correspondingSliderHeadForObject[h]; - return (sliderHead.StartTime - sliderHead.TimePreempt + sliderHead.TimeFadeIn, sliderHead.TimePreempt * fade_out_duration_multiplier); + case DrawableSliderRepeat repeat: + // Use the same fade sequence as the slider head. + Debug.Assert(repeat.Slider != null); + return getParameters(repeat.Slider.HeadCircle); + + default: + return getParameters(drawableObject.HitObject); + } + + static (double startTime, double duration) getParameters(OsuHitObject hitObject) + { + var fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn; + var fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier; + + // new duration from completed fade in to end (before fading out) + var longFadeDuration = hitObject.GetEndTime() - fadeOutStartTime; + + switch (hitObject) + { + case Slider _: + return (fadeOutStartTime, longFadeDuration); + + case SliderTick _: + var tickFadeOutDuration = Math.Min(hitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); + return (hitObject.StartTime - tickFadeOutDuration, tickFadeOutDuration); + + case Spinner _: + return (fadeOutStartTime + longFadeDuration, fadeOutDuration); + + default: + return (fadeOutStartTime, fadeOutDuration); + } + } } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 3a92938d75..e878d61eec 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Drawables; @@ -11,14 +13,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderHead : DrawableHitCircle { + [CanBeNull] + public Slider Slider => drawableSlider?.HitObject; + private readonly IBindable pathVersion = new Bindable(); protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle; private DrawableSlider drawableSlider; - private Slider slider => drawableSlider?.HitObject; - public DrawableSliderHead() { } @@ -58,11 +61,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.Update(); - double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); + Debug.Assert(Slider != null); + + double completionProgress = Math.Clamp((Time.Current - Slider.StartTime) / Slider.Duration, 0, 1); //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. if (!IsHit) - Position = slider.CurvePositionAt(completionProgress); + Position = Slider.CurvePositionAt(completionProgress); } public Action OnShake; @@ -71,8 +76,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void updatePosition() { - if (slider != null) - Position = HitObject.Position - slider.Position; + if (Slider != null) + Position = HitObject.Position - Slider.Position; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 0735d48ae1..ba503cca6a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,6 +19,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public new SliderRepeat HitObject => (SliderRepeat)base.HitObject; + [CanBeNull] + public Slider Slider => drawableSlider?.HitObject; + private double animDuration; public Drawable CirclePiece { get; private set; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index eff72168ee..3deff55538 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -15,6 +16,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject; + [CanBeNull] + public Slider Slider => drawableSlider?.HitObject; + /// /// The judgement text is provided by the . /// @@ -22,6 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool Tracking { get; set; } + private DrawableSlider drawableSlider; private SkinnableDrawable circlePiece; private Container scaleContainer; @@ -59,6 +64,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); } + protected override void OnParentReceived(DrawableHitObject parent) + { + base.OnParentReceived(parent); + + drawableSlider = (DrawableSlider)parent; + } + protected override void UpdateInitialTransforms() { base.UpdateInitialTransforms(); From db7e82c5603b8df224a0c933ced3ae255aeac340 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Dec 2020 14:54:02 +0900 Subject: [PATCH 363/394] Add test --- .../Mods/TestSceneOsuModHidden.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs index 40f1c4a52f..ff308f389f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -92,6 +92,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods PassCondition = checkSomeHit }); + [Test] + public void TestWithSliderReuse() => CreateModTest(new ModTestData + { + Mod = new OsuModHidden(), + Autoplay = true, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 1000, + Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + }, + new Slider + { + StartTime = 4000, + Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + }, + } + }, + PassCondition = checkSomeHit + }); + private bool checkSomeHit() { return Player.ScoreProcessor.JudgedHits >= 4; From 31f7f7072deb281f1bcf4fc0e3cf6208e8e27d24 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Dec 2020 15:13:20 +0900 Subject: [PATCH 364/394] Fix song select panels not loading if partially offscreen --- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index e25c6932cf..b3c5d458d6 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -100,8 +100,14 @@ namespace osu.Game.Screens.Select.Carousel background = new DelayedLoadWrapper(() => new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, - }, 300), - mainFlow = new DelayedLoadWrapper(() => new SetPanelContent((CarouselBeatmapSet)Item), 100), + }, 300) + { + RelativeSizeAxes = Axes.Both + }, + mainFlow = new DelayedLoadWrapper(() => new SetPanelContent((CarouselBeatmapSet)Item), 100) + { + RelativeSizeAxes = Axes.Both + }, }; background.DelayedLoadComplete += fadeContentIn; From 3550e5b30f93d965265bbf4ab611a588753a23b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Dec 2020 16:42:06 +0900 Subject: [PATCH 365/394] Add length display to room screen as well --- .../Online/API/Requests/Responses/APIBeatmap.cs | 1 + .../Online/Multiplayer/PlaylistExtensions.cs | 16 ++++++++++++++++ .../Match/Components/MatchSettingsOverlay.cs | 9 ++------- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 5 ++++- 4 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/PlaylistExtensions.cs diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index ae65ac09b2..7343870dbc 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -75,6 +75,7 @@ namespace osu.Game.Online.API.Requests.Responses StarDifficulty = starDifficulty, OnlineBeatmapID = OnlineBeatmapID, Version = version, + // this is actually an incorrect mapping (Length is calculated as drain length in lazer's import process, see BeatmapManager.calculateLength). Length = TimeSpan.FromSeconds(length).TotalMilliseconds, Status = Status, BeatmapSet = set, diff --git a/osu.Game/Online/Multiplayer/PlaylistExtensions.cs b/osu.Game/Online/Multiplayer/PlaylistExtensions.cs new file mode 100644 index 0000000000..fe3d96e295 --- /dev/null +++ b/osu.Game/Online/Multiplayer/PlaylistExtensions.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using Humanizer; +using Humanizer.Localisation; +using osu.Framework.Bindables; + +namespace osu.Game.Online.Multiplayer +{ + public static class PlaylistExtensions + { + public static string GetTotalDuration(this BindableList playlist) => + playlist.Select(p => p.Beatmap.Value.Length).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs index 8bf66b084c..668a373d80 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Specialized; -using System.Linq; using Humanizer; -using Humanizer.Localisation; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -339,11 +337,8 @@ namespace osu.Game.Screens.Multi.Match.Components ApplyButton.Enabled.Value = hasValidSettings; } - private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) - { - double totalLength = Playlist.Select(p => p.Beatmap.Value.Length).Sum(); - playlistLength.Text = $"Length: {totalLength.Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2)}"; - } + private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => + playlistLength.Text = $"Length: {Playlist.GetTotalDuration()}"; private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0; diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 2cbe215a39..13a5d89a12 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -57,6 +57,7 @@ namespace osu.Game.Screens.Multi.Match private IBindable> managerUpdated; private OverlinedHeader participantsHeader; + private OverlinedHeader playlistHeader; public MatchSubScreen(Room room) { @@ -135,7 +136,7 @@ namespace osu.Game.Screens.Multi.Match RelativeSizeAxes = Axes.Both, Content = new[] { - new Drawable[] { new OverlinedHeader("Playlist"), }, + new Drawable[] { playlistHeader = new OverlinedHeader("Playlist"), }, new Drawable[] { new DrawableRoomPlaylistWithResults @@ -243,6 +244,8 @@ namespace osu.Game.Screens.Multi.Match managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); + + playlist.BindCollectionChanged((_, __) => playlistHeader.Details.Value = playlist.GetTotalDuration(), true); } public override bool OnExiting(IScreen next) From e5c8e06c4bd44ace592b8a0549d524bc582e41d2 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 19:28:47 +0900 Subject: [PATCH 366/394] Create children in the constructor --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 33 +++++++++++++-------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 11b6916a4c..d164d2e0ca 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -49,11 +49,7 @@ namespace osu.Game.Rulesets.Catch.UI public Container ExplodingFruitTarget; - private Container caughtFruitContainer { get; } = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - }; + private readonly Container caughtFruitContainer; [NotNull] private readonly Container trailsTarget; @@ -92,9 +88,9 @@ namespace osu.Game.Rulesets.Catch.UI /// private readonly float catchWidth; - private CatcherSprite catcherIdle; - private CatcherSprite catcherKiai; - private CatcherSprite catcherFail; + private readonly CatcherSprite catcherIdle; + private readonly CatcherSprite catcherKiai; + private readonly CatcherSprite catcherFail; private CatcherSprite currentCatcher; @@ -108,8 +104,8 @@ namespace osu.Game.Rulesets.Catch.UI private float hyperDashTargetPosition; private Bindable hitLighting; - private DrawablePool hitExplosionPool; - private Container hitExplosionContainer; + private readonly DrawablePool hitExplosionPool; + private readonly Container hitExplosionContainer; public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null) { @@ -122,17 +118,15 @@ namespace osu.Game.Rulesets.Catch.UI Scale = calculateScale(difficulty); catchWidth = CalculateCatchWidth(Scale); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - hitLighting = config.GetBindable(OsuSetting.HitLighting); InternalChildren = new Drawable[] { hitExplosionPool = new DrawablePool(10), - caughtFruitContainer, + caughtFruitContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + }, catcherIdle = new CatcherSprite(CatcherAnimationState.Idle) { Anchor = Anchor.TopCentre, @@ -154,7 +148,12 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.BottomCentre, }, }; + } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + hitLighting = config.GetBindable(OsuSetting.HitLighting); trails = new CatcherTrailDisplay(this); updateCatcher(); From af45e8d97b2ff17b05917e6230a3e5353a43e6b4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 19:45:34 +0900 Subject: [PATCH 367/394] Don't delay caught fruit loading It is not needed anymore because some code in DCHO is moved from `load` to constructor. --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 26077aeba4..467dc4283d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -28,8 +27,6 @@ namespace osu.Game.Rulesets.Catch.UI set => MovableCatcher.ExplodingFruitTarget = value; } - private DrawableCatchHitObject lastPlateableFruit; - public CatcherArea(BeatmapDifficulty difficulty = null) { Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); @@ -53,19 +50,6 @@ namespace osu.Game.Rulesets.Catch.UI if (!result.Type.IsScorable()) return; - void runAfterLoaded(Action action) - { - if (lastPlateableFruit == null) - return; - - // this is required to make this run after the last caught fruit runs updateState() at least once. - // TODO: find a better alternative - if (lastPlateableFruit.IsLoaded) - action(); - else - lastPlateableFruit.OnLoadComplete += _ => action(); - } - if (result.IsHit && hitObject is DrawablePalpableCatchHitObject fruit) { // create a new (cloned) fruit to stay on the plate. the original is faded out immediately. @@ -84,16 +68,15 @@ namespace osu.Game.Rulesets.Catch.UI caughtFruit.LifetimeEnd = double.MaxValue; MovableCatcher.PlaceOnPlate(caughtFruit); - lastPlateableFruit = caughtFruit; if (!fruit.StaysOnPlate) - runAfterLoaded(() => MovableCatcher.Explode(caughtFruit)); + MovableCatcher.Explode(caughtFruit); } if (hitObject.HitObject.LastInCombo) { if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result)) - runAfterLoaded(() => MovableCatcher.Explode()); + MovableCatcher.Explode(); else MovableCatcher.Drop(); } From a231a4aa6d127c2ebad93b562c45370458837834 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 19:46:30 +0900 Subject: [PATCH 368/394] Remove unused method --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 467dc4283d..a2a4bd5304 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -87,10 +87,6 @@ namespace osu.Game.Rulesets.Catch.UI public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result) => comboDisplay.OnRevertResult(fruit, result); - public void OnReleased(CatchAction action) - { - } - public bool AttemptCatch(CatchHitObject obj) { return MovableCatcher.AttemptCatch(obj); From 8d32cca5d69d7d12ebbbce28b6d9cfc1e8259d04 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 20:03:13 +0900 Subject: [PATCH 369/394] Use more specific type for caught object --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 12 ++++++------ osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index d164d2e0ca..da71145004 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.UI public Container ExplodingFruitTarget; - private readonly Container caughtFruitContainer; + private readonly Container caughtFruitContainer; [NotNull] private readonly Container trailsTarget; @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Catch.UI InternalChildren = new Drawable[] { hitExplosionPool = new DrawablePool(10), - caughtFruitContainer = new Container + caughtFruitContainer = new Container { Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, @@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Catch.UI /// Add a caught fruit to the catcher's stack. /// /// The fruit that was caught. - public void PlaceOnPlate(DrawableCatchHitObject fruit) + public void PlaceOnPlate(DrawablePalpableCatchHitObject fruit) { var ourRadius = fruit.DisplayRadius; float theirRadius = 0; @@ -385,7 +385,7 @@ namespace osu.Game.Rulesets.Catch.UI Explode(f); } - public void Drop(DrawableHitObject fruit) + public void Drop(DrawablePalpableCatchHitObject fruit) { removeFromPlateWithTransform(fruit, f => { @@ -394,7 +394,7 @@ namespace osu.Game.Rulesets.Catch.UI }); } - public void Explode(DrawableHitObject fruit) + public void Explode(DrawablePalpableCatchHitObject fruit) { var originalX = fruit.X * Scale.X; @@ -478,7 +478,7 @@ namespace osu.Game.Rulesets.Catch.UI updateCatcher(); } - private void removeFromPlateWithTransform(DrawableHitObject fruit, Action action) + private void removeFromPlateWithTransform(DrawablePalpableCatchHitObject fruit, Action action) { if (ExplodingFruitTarget != null) { diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index a2a4bd5304..bb5eaaa438 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Catch.UI comboDisplay.X = MovableCatcher.X; } - private DrawableCatchHitObject createCaughtFruit(DrawablePalpableCatchHitObject hitObject) + private DrawablePalpableCatchHitObject createCaughtFruit(DrawablePalpableCatchHitObject hitObject) { switch (hitObject.HitObject) { From 873f2363c1f757e662845c33b015bd096f5de68c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 20:05:01 +0900 Subject: [PATCH 370/394] Simplify the fruit stacking code It is now more clear that the expression of distance checking is probably unintended (a bug) --- .../Objects/Drawables/DrawableCatchHitObject.cs | 2 -- .../Objects/Drawables/DrawablePalpableCatchHitObject.cs | 2 ++ osu.Game.Rulesets.Catch/UI/Catcher.cs | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 1faa6a5b0f..c3dbfc325f 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -16,8 +16,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables protected override double InitialLifetimeOffset => HitObject.TimePreempt; - public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale; - protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH; protected DrawableCatchHitObject([CanBeNull] CatchHitObject hitObject) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs index a3908f94b6..0877b5e248 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables /// public virtual bool StaysOnPlate => true; + public float DisplayRadius => CatchHitObject.OBJECT_RADIUS * HitObject.Scale * ScaleFactor; + protected readonly Container ScaleContainer; protected DrawablePalpableCatchHitObject([CanBeNull] CatchHitObject h) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index da71145004..94383516bd 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -204,8 +204,7 @@ namespace osu.Game.Rulesets.Catch.UI const float allowance = 10; while (caughtFruitContainer.Any(f => - f.LifetimeEnd == double.MaxValue && - Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2))) + Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = CatchHitObject.OBJECT_RADIUS / 2)) / (allowance / 2))) { var diff = (ourRadius + theirRadius) / allowance; fruit.X += (RNG.NextSingle() - 0.5f) * diff * 2; From 2eb2c934ccbd3360c393403dcdbb2cb87c8a0dcb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 2 Dec 2020 21:23:34 +0900 Subject: [PATCH 371/394] Refactor fruit dropping code - The repeated `Remove` call was quadratic complexity. Now it is linear time. --- .../TestSceneCatcher.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 1 - osu.Game.Rulesets.Catch/UI/Catcher.cs | 119 +++++++++--------- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 7 +- 4 files changed, 65 insertions(+), 64 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 6eeda2c731..cb4aaefa46 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Tests [BackgroundDependencyLoader] private void load() { - SetContents(() => new Catcher(new Container()) + SetContents(() => new Catcher(new Container(), new Container()) { RelativePositionAxes = Axes.None, Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 820f08d439..6934dcc1f9 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -43,7 +43,6 @@ namespace osu.Game.Rulesets.Catch.UI CatcherArea = new CatcherArea(difficulty) { - ExplodingFruitTarget = explodingFruitContainer, Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, }; diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 94383516bd..f8ed51bd6f 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -17,7 +17,6 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Skinning; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -47,15 +46,15 @@ namespace osu.Game.Rulesets.Catch.UI /// public const double BASE_SPEED = 1.0; - public Container ExplodingFruitTarget; - - private readonly Container caughtFruitContainer; - [NotNull] private readonly Container trailsTarget; private CatcherTrailDisplay trails; + private readonly Container droppedObjectTarget; + + private readonly Container caughtFruitContainer; + public CatcherAnimationState CurrentState { get; private set; } /// @@ -107,9 +106,10 @@ namespace osu.Game.Rulesets.Catch.UI private readonly DrawablePool hitExplosionPool; private readonly Container hitExplosionContainer; - public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null) + public Catcher([NotNull] Container trailsTarget, [NotNull] Container droppedObjectTarget, BeatmapDifficulty difficulty = null) { this.trailsTarget = trailsTarget; + this.droppedObjectTarget = droppedObjectTarget; Origin = Anchor.TopCentre; @@ -369,41 +369,14 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Drop any fruit off the plate. /// - public void Drop() - { - foreach (var f in caughtFruitContainer.ToArray()) - Drop(f); - } + public void Drop() => clearPlate(DroppedObjectAnimation.Drop); /// /// Explode any fruit off the plate. /// - public void Explode() - { - foreach (var f in caughtFruitContainer.ToArray()) - Explode(f); - } + public void Explode() => clearPlate(DroppedObjectAnimation.Explode); - public void Drop(DrawablePalpableCatchHitObject fruit) - { - removeFromPlateWithTransform(fruit, f => - { - f.MoveToY(f.Y + 75, 750, Easing.InSine); - f.FadeOut(750); - }); - } - - public void Explode(DrawablePalpableCatchHitObject fruit) - { - var originalX = fruit.X * Scale.X; - - removeFromPlateWithTransform(fruit, f => - { - f.MoveToY(f.Y - 50, 250, Easing.OutSine).Then().MoveToY(f.Y + 50, 500, Easing.InSine); - f.MoveToX(f.X + originalX * 6, 1000); - f.FadeOut(750); - }); - } + public void Explode(DrawablePalpableCatchHitObject caughtObject) => removeFromPlate(caughtObject, DroppedObjectAnimation.Explode); protected override void SkinChanged(ISkinSource skin, bool allowFallback) { @@ -477,33 +450,67 @@ namespace osu.Game.Rulesets.Catch.UI updateCatcher(); } - private void removeFromPlateWithTransform(DrawablePalpableCatchHitObject fruit, Action action) + private void clearPlate(DroppedObjectAnimation animation) { - if (ExplodingFruitTarget != null) + var caughtObjects = caughtFruitContainer.Children.ToArray(); + caughtFruitContainer.Clear(false); + + droppedObjectTarget.AddRange(caughtObjects); + + foreach (var caughtObject in caughtObjects) + drop(caughtObject, animation); + } + + private void removeFromPlate(DrawablePalpableCatchHitObject caughtObject, DroppedObjectAnimation animation) + { + if (!caughtFruitContainer.Remove(caughtObject)) + throw new InvalidOperationException("Can only drop a caught object on the plate"); + + droppedObjectTarget.Add(caughtObject); + + drop(caughtObject, animation); + } + + private void drop(Drawable d, DroppedObjectAnimation animation) + { + var originalX = d.X * Scale.X; + + d.Anchor = Anchor.TopLeft; + d.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(d.DrawPosition, droppedObjectTarget); + + animate(d, animation, originalX); + } + + private void animate(Drawable d, DroppedObjectAnimation animation, float originalX) + { + // temporary hack to make sure transforms are not cleared by DHO state update + if (!d.IsLoaded) { - fruit.Anchor = Anchor.TopLeft; - fruit.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget); - - if (!caughtFruitContainer.Remove(fruit)) - // we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling). - // this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice. - return; - - ExplodingFruitTarget.Add(fruit); + d.OnLoadComplete += _ => animate(d, animation, originalX); + return; } - var actionTime = Clock.CurrentTime; - - fruit.ApplyCustomUpdateState += onFruitOnApplyCustomUpdateState; - onFruitOnApplyCustomUpdateState(fruit, fruit.State.Value); - - void onFruitOnApplyCustomUpdateState(DrawableHitObject o, ArmedState state) + switch (animation) { - using (fruit.BeginAbsoluteSequence(actionTime)) - action(fruit); + case DroppedObjectAnimation.Drop: + d.MoveToY(d.Y + 75, 750, Easing.InSine); + d.FadeOut(750); + break; - fruit.Expire(); + case DroppedObjectAnimation.Explode: + d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine); + d.MoveToX(d.X + originalX * 6, 1000); + d.FadeOut(750); + break; } + + d.Expire(); } } + + public enum DroppedObjectAnimation + { + Drop, + Explode + } } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index bb5eaaa438..077137a3cb 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -22,11 +22,6 @@ namespace osu.Game.Rulesets.Catch.UI public readonly Catcher MovableCatcher; private readonly CatchComboDisplay comboDisplay; - public Container ExplodingFruitTarget - { - set => MovableCatcher.ExplodingFruitTarget = value; - } - public CatcherArea(BeatmapDifficulty difficulty = null) { Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); @@ -41,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI Margin = new MarginPadding { Bottom = 350f }, X = CatchPlayfield.CENTER_X }, - MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X }, + MovableCatcher = new Catcher(this, this, difficulty) { X = CatchPlayfield.CENTER_X }, }; } From 5a5c956cedfda99b3280c9d7bb7933f267fea821 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 3 Dec 2020 14:44:35 +0900 Subject: [PATCH 372/394] Move more logic to Catcher from CatcherArea --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 74 ++++++++++++++++++----- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 44 -------------- 2 files changed, 60 insertions(+), 58 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index f8ed51bd6f..6d5d25243d 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -245,25 +245,29 @@ namespace osu.Game.Rulesets.Catch.UI catchObjectPosition >= catcherPosition - halfCatchWidth && catchObjectPosition <= catcherPosition + halfCatchWidth; - // only update hyperdash state if we are not catching a tiny droplet. - if (fruit is TinyDroplet) return validCatch; - - if (validCatch && fruit.HyperDash) + // droplet doesn't affect the catcher state + if (!(fruit is TinyDroplet)) { - var target = fruit.HyperDashTarget; - var timeDifference = target.StartTime - fruit.StartTime; - double positionDifference = target.X - catcherPosition; - var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0); + if (validCatch && fruit.HyperDash) + { + var target = fruit.HyperDashTarget; + var timeDifference = target.StartTime - fruit.StartTime; + double positionDifference = target.X - catcherPosition; + var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0); - SetHyperDashState(Math.Abs(velocity), target.X); + SetHyperDashState(Math.Abs(velocity), target.X); + } + else + SetHyperDashState(); + + if (validCatch) + updateState(fruit.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle); + else if (!(fruit is Banana)) + updateState(CatcherAnimationState.Fail); } - else - SetHyperDashState(); if (validCatch) - updateState(fruit.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle); - else if (!(fruit is Banana)) - updateState(CatcherAnimationState.Fail); + placeCaughtObject(fruit); return validCatch; } @@ -450,6 +454,48 @@ namespace osu.Game.Rulesets.Catch.UI updateCatcher(); } + private void placeCaughtObject(PalpableCatchHitObject source) + { + var caughtObject = createCaughtObject(source); + if (caughtObject == null) return; + + caughtObject.RelativePositionAxes = Axes.None; + caughtObject.X = source.X - X; + caughtObject.IsOnPlate = true; + + caughtObject.Anchor = Anchor.TopCentre; + caughtObject.Origin = Anchor.Centre; + caughtObject.Scale *= 0.5f; + caughtObject.LifetimeStart = source.StartTime; + caughtObject.LifetimeEnd = double.MaxValue; + + PlaceOnPlate(caughtObject); + + if (!caughtObject.StaysOnPlate) + Explode(caughtObject); + } + + private DrawablePalpableCatchHitObject createCaughtObject(PalpableCatchHitObject source) + { + switch (source) + { + case Banana banana: + return new DrawableBanana(banana); + + case Fruit fruit: + return new DrawableFruit(fruit); + + case TinyDroplet tiny: + return new DrawableTinyDroplet(tiny); + + case Droplet droplet: + return new DrawableDroplet(droplet); + + default: + return null; + } + } + private void clearPlate(DroppedObjectAnimation animation) { var caughtObjects = caughtFruitContainer.Children.ToArray(); diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 077137a3cb..9cd0785b85 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -45,29 +45,6 @@ namespace osu.Game.Rulesets.Catch.UI if (!result.Type.IsScorable()) return; - if (result.IsHit && hitObject is DrawablePalpableCatchHitObject fruit) - { - // create a new (cloned) fruit to stay on the plate. the original is faded out immediately. - var caughtFruit = createCaughtFruit(fruit); - - if (caughtFruit == null) return; - - caughtFruit.RelativePositionAxes = Axes.None; - caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(hitObject.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0); - caughtFruit.IsOnPlate = true; - - caughtFruit.Anchor = Anchor.TopCentre; - caughtFruit.Origin = Anchor.Centre; - caughtFruit.Scale *= 0.5f; - caughtFruit.LifetimeStart = caughtFruit.HitObject.StartTime; - caughtFruit.LifetimeEnd = double.MaxValue; - - MovableCatcher.PlaceOnPlate(caughtFruit); - - if (!fruit.StaysOnPlate) - MovableCatcher.Explode(caughtFruit); - } - if (hitObject.HitObject.LastInCombo) { if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result)) @@ -98,26 +75,5 @@ namespace osu.Game.Rulesets.Catch.UI comboDisplay.X = MovableCatcher.X; } - - private DrawablePalpableCatchHitObject createCaughtFruit(DrawablePalpableCatchHitObject hitObject) - { - switch (hitObject.HitObject) - { - case Banana banana: - return new DrawableBanana(banana); - - case Fruit fruit: - return new DrawableFruit(fruit); - - case TinyDroplet tiny: - return new DrawableTinyDroplet(tiny); - - case Droplet droplet: - return new DrawableDroplet(droplet); - - default: - return null; - } - } } } From 1d669cf65ed5c9ee5e398a3cac7e5bc386b5f49a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 3 Dec 2020 16:40:14 +0900 Subject: [PATCH 373/394] Add more TestSceneCatcher tests Some tests are moved from TestSceneCatcherArea --- .../TestSceneCatcher.cs | 178 +++++++++++++++++- .../TestSceneCatcherArea.cs | 81 +++----- 2 files changed, 195 insertions(+), 64 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index cb4aaefa46..f41a16026a 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -1,26 +1,192 @@ // 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.Game.Rulesets.Catch.UI; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneCatcher : CatchSkinnableTestScene + public class TestSceneCatcher : OsuTestScene { - [BackgroundDependencyLoader] - private void load() + [Resolved] + private OsuConfigManager config { get; set; } + + private Container droppedObjectContainer; + + private TestCatcher catcher; + + [SetUp] + public void SetUp() => Schedule(() => { - SetContents(() => new Catcher(new Container(), new Container()) + var difficulty = new BeatmapDifficulty + { + CircleSize = 0, + }; + + var trailContainer = new Container(); + droppedObjectContainer = new Container(); + catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty); + + Child = new Container { - RelativePositionAxes = Axes.None, Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Children = new Drawable[] + { + trailContainer, + droppedObjectContainer, + catcher + } + }; + }); + + [Test] + public void TestCatcherCatchWidth() + { + var halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2; + AddStep("catch fruit", () => + { + attemptCatch(new Fruit { X = -halfWidth + 1 }); + attemptCatch(new Fruit { X = halfWidth - 1 }); }); + checkPlate(2); + AddStep("miss fruit", () => + { + attemptCatch(new Fruit { X = -halfWidth - 1 }); + attemptCatch(new Fruit { X = halfWidth + 1 }); + }); + checkPlate(2); + } + + [Test] + public void TestCatcherStateFruit() + { + AddStep("miss fruit", () => attemptCatch(new Fruit { X = 100 })); + checkState(CatcherAnimationState.Fail); + AddStep("catch fruit", () => attemptCatch(new Fruit())); + checkState(CatcherAnimationState.Idle); + AddStep("catch kiai fruit", () => attemptCatch(new TestKiaiFruit())); + checkState(CatcherAnimationState.Kiai); + } + + [Test] + public void TestCatcherStateTinyDroplet() + { + AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit + { + HyperDashTarget = new Fruit { X = 100 } + })); + AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet())); + AddStep("miss tiny droplet", () => attemptCatch(new TinyDroplet { X = 100 })); + checkState(CatcherAnimationState.Kiai); + checkHyperDash(true); + } + + [Test] + public void TestCatcherStateBanana() + { + AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit + { + HyperDashTarget = new Fruit { X = 100 } + })); + AddStep("miss banana", () => attemptCatch(new Banana())); + checkState(CatcherAnimationState.Idle); + checkHyperDash(false); + } + + [Test] + public void TestCatcherStacking() + { + AddStep("catch fruit", () => attemptCatch(new Fruit())); + checkPlate(1); + AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9)); + checkPlate(10); + AddAssert("caught objects are stacked", () => + catcher.CaughtObjects.All(obj => obj.Y <= 0) && + catcher.CaughtObjects.Any(obj => obj.Y == 0) && + catcher.CaughtObjects.Any(obj => obj.Y < -20)); + } + + [Test] + public void TestCatcherExplosionAndDropping() + { + AddStep("catch fruit", () => attemptCatch(new Fruit())); + AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet())); + AddAssert("tiny droplet is exploded", () => catcher.CaughtObjects.Count() == 1 && droppedObjectContainer.Count == 1); + AddUntilStep("wait explosion", () => !droppedObjectContainer.Any()); + AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9)); + AddStep("explode", () => catcher.Explode()); + AddAssert("fruits are exploded", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10); + AddUntilStep("wait explosion", () => !droppedObjectContainer.Any()); + AddStep("catch fruits", () => attemptCatch(new Fruit(), 10)); + AddStep("drop", () => catcher.Drop()); + AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10); + } + + [Test] + public void TestHyperFruitHyperDash() + { + AddStep("catch hyper fruit", () => attemptCatch(new Fruit + { + HyperDashTarget = new Fruit { X = 100 } + })); + checkHyperDash(true); + AddStep("catch normal fruit", () => attemptCatch(new Fruit())); + checkHyperDash(false); + } + + [TestCase(true)] + [TestCase(false)] + public void TestHitLighting(bool enabled) + { + AddStep($"{(enabled ? "enable" : "disable")} hit lighting", () => config.Set(OsuSetting.HitLighting, enabled)); + AddStep("catch fruit", () => attemptCatch(new Fruit())); + AddAssert("check hit lighting", () => catcher.ChildrenOfType().Any() == enabled); + } + + private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count); + + private void checkState(CatcherAnimationState state) => AddAssert($"catcher state is {state}", () => catcher.CurrentState == state); + + private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state); + + private void attemptCatch(CatchHitObject hitObject, int count = 1) + { + hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + for (var i = 0; i < count; i++) + catcher.AttemptCatch(hitObject); + } + + public class TestCatcher : Catcher + { + public IEnumerable CaughtObjects => this.ChildrenOfType(); + + public TestCatcher(Container trailsTarget, Container droppedObjectTarget, BeatmapDifficulty difficulty) + : base(trailsTarget, droppedObjectTarget, difficulty) + { + } + } + + public class TestKiaiFruit : Fruit + { + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + } } } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index c12f38723b..7be6fc92ac 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -27,73 +27,51 @@ namespace osu.Game.Rulesets.Catch.Tests [Resolved] private OsuConfigManager config { get; set; } - private Catcher catcher => this.ChildrenOfType().First().MovableCatcher; + private Catcher catcher => this.ChildrenOfType().First(); + + private float circleSize; public TestSceneCatcherArea() { - AddSliderStep("CircleSize", 0, 8, 5, createCatcher); - AddToggleStep("Hyperdash", t => - CreatedDrawables.OfType().Select(i => i.Child) - .OfType().ForEach(c => c.ToggleHyperDash(t))); + AddSliderStep("circle size", 0, 8, 5, createCatcher); + AddToggleStep("hyper dash", t => this.ChildrenOfType().ForEach(area => area.ToggleHyperDash(t))); - AddRepeatStep("catch fruit", () => catchFruit(new TestFruit(false) - { - X = catcher.X - }), 20); - AddRepeatStep("catch fruit last in combo", () => catchFruit(new TestFruit(false) - { - X = catcher.X, - LastInCombo = true, - }), 20); - AddRepeatStep("catch kiai fruit", () => catchFruit(new TestFruit(true) - { - X = catcher.X - }), 20); - AddRepeatStep("miss fruit", () => catchFruit(new Fruit - { - X = catcher.X + 100, - LastInCombo = true, - }, true), 20); + AddStep("catch fruit", () => attemptCatch(new Fruit())); + AddStep("catch fruit last in combo", () => attemptCatch(new Fruit { LastInCombo = true })); + AddStep("catch kiai fruit", () => attemptCatch(new TestSceneCatcher.TestKiaiFruit())); + AddStep("miss last in combo", () => attemptCatch(new Fruit { X = 100, LastInCombo = true })); } - [TestCase(true)] - [TestCase(false)] - public void TestHitLighting(bool enable) + private void attemptCatch(Fruit fruit) { - AddStep("create catcher", () => createCatcher(5)); - - AddStep("toggle hit lighting", () => config.Set(OsuSetting.HitLighting, enable)); - AddStep("catch fruit", () => catchFruit(new TestFruit(false) + fruit.X += catcher.X; + fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { - X = catcher.X - })); - AddStep("catch fruit last in combo", () => catchFruit(new TestFruit(false) - { - X = catcher.X, - LastInCombo = true - })); - AddAssert("check hit explosion", () => catcher.ChildrenOfType().Any() == enable); - } + CircleSize = circleSize + }); - private void catchFruit(Fruit fruit, bool miss = false) - { - this.ChildrenOfType().ForEach(area => + foreach (var area in this.ChildrenOfType()) { DrawableFruit drawable = new DrawableFruit(fruit); area.Add(drawable); Schedule(() => { - area.AttemptCatch(fruit); - area.OnNewResult(drawable, new JudgementResult(fruit, new CatchJudgement()) { Type = miss ? HitResult.Miss : HitResult.Great }); + bool caught = area.AttemptCatch(fruit); + area.OnNewResult(drawable, new JudgementResult(fruit, new CatchJudgement()) + { + Type = caught ? HitResult.Great : HitResult.Miss + }); drawable.Expire(); }); - }); + } } private void createCatcher(float size) { + circleSize = size; + SetContents(() => new CatchInputManager(catchRuleset) { RelativeSizeAxes = Axes.Both, @@ -111,17 +89,6 @@ namespace osu.Game.Rulesets.Catch.Tests catchRuleset = rulesets.GetRuleset(2); } - public class TestFruit : Fruit - { - public TestFruit(bool kiai) - { - var kiaiCpi = new ControlPointInfo(); - kiaiCpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); - - ApplyDefaultsToSelf(kiaiCpi, new BeatmapDifficulty()); - } - } - private class TestCatcherArea : CatcherArea { public TestCatcherArea(BeatmapDifficulty beatmapDifficulty) @@ -129,8 +96,6 @@ namespace osu.Game.Rulesets.Catch.Tests { } - public new Catcher MovableCatcher => base.MovableCatcher; - public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1); } } From c64343c7d719ed8c5c3dc1bfa35934145ab2ffad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Dec 2020 17:42:41 +0900 Subject: [PATCH 374/394] 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 9d99218f88..9a3d42d6b7 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4b931726e0..9d37ceee6c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 3a47b77820..ab03393836 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - + From 78c43641d1c6022e1143280843543fec65b678f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Dec 2020 17:43:09 +0900 Subject: [PATCH 375/394] Update imagesharp namespaces (and consume System.Drawing types instead) --- osu.Game.Tournament/Models/TournamentMatch.cs | 2 +- osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs | 2 +- .../Screens/Ladder/Components/DrawableTournamentMatch.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Models/TournamentMatch.cs b/osu.Game.Tournament/Models/TournamentMatch.cs index 8ebcbf4e15..bdfb1728f3 100644 --- a/osu.Game.Tournament/Models/TournamentMatch.cs +++ b/osu.Game.Tournament/Models/TournamentMatch.cs @@ -4,10 +4,10 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Drawing; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Game.Tournament.Screens.Ladder.Components; -using SixLabors.Primitives; namespace osu.Game.Tournament.Models { diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index efec4cffdd..ca46c3b050 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Drawing; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -16,7 +17,6 @@ using osu.Game.Tournament.Screens.Ladder; using osu.Game.Tournament.Screens.Ladder.Components; using osuTK; using osuTK.Graphics; -using SixLabors.Primitives; namespace osu.Game.Tournament.Screens.Editors { diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs index f2065e7e88..1c805bb42e 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -13,7 +14,6 @@ using osu.Game.Tournament.Models; using osuTK; using osuTK.Graphics; using osuTK.Input; -using SixLabors.Primitives; namespace osu.Game.Tournament.Screens.Ladder.Components { From 3e62da119eca2d34c3e24b1bf61ec68a6eeaddf9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Dec 2020 17:59:39 +0900 Subject: [PATCH 376/394] Add to inspector also --- .../Components/OverlinedPlaylistHeader.cs | 22 +++++++++++++++++++ .../Multi/Lounge/Components/RoomInspector.cs | 2 +- .../Screens/Multi/Match/MatchSubScreen.cs | 5 +---- 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/Multi/Components/OverlinedPlaylistHeader.cs diff --git a/osu.Game/Screens/Multi/Components/OverlinedPlaylistHeader.cs b/osu.Game/Screens/Multi/Components/OverlinedPlaylistHeader.cs new file mode 100644 index 0000000000..02dbe501b1 --- /dev/null +++ b/osu.Game/Screens/Multi/Components/OverlinedPlaylistHeader.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.Multiplayer; + +namespace osu.Game.Screens.Multi.Components +{ + public class OverlinedPlaylistHeader : OverlinedHeader + { + public OverlinedPlaylistHeader() + : base("Playlist") + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Playlist.BindCollectionChanged((_, __) => Details.Value = Playlist.GetTotalDuration()); + } + } +} diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs index 77fbd606f4..dfee278e87 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components } } }, - new Drawable[] { new OverlinedHeader("Playlist"), }, + new Drawable[] { new OverlinedPlaylistHeader(), }, new Drawable[] { new DrawableRoomPlaylist(false, false) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 13a5d89a12..2f8aad4e65 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -57,7 +57,6 @@ namespace osu.Game.Screens.Multi.Match private IBindable> managerUpdated; private OverlinedHeader participantsHeader; - private OverlinedHeader playlistHeader; public MatchSubScreen(Room room) { @@ -136,7 +135,7 @@ namespace osu.Game.Screens.Multi.Match RelativeSizeAxes = Axes.Both, Content = new[] { - new Drawable[] { playlistHeader = new OverlinedHeader("Playlist"), }, + new Drawable[] { new OverlinedPlaylistHeader(), }, new Drawable[] { new DrawableRoomPlaylistWithResults @@ -244,8 +243,6 @@ namespace osu.Game.Screens.Multi.Match managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); - - playlist.BindCollectionChanged((_, __) => playlistHeader.Details.Value = playlist.GetTotalDuration(), true); } public override bool OnExiting(IScreen next) From 62b1e37f73701a71992d23b4d987f6f5854a7fae Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Dec 2020 18:04:53 +0900 Subject: [PATCH 377/394] Use async overloads --- osu.Game/Graphics/ScreenshotManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index d1f6fd445e..53ee711626 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -116,13 +116,13 @@ namespace osu.Game.Graphics switch (screenshotFormat.Value) { case ScreenshotFormat.Png: - image.SaveAsPng(stream); + await image.SaveAsPngAsync(stream); break; case ScreenshotFormat.Jpg: const int jpeg_quality = 92; - image.SaveAsJpeg(stream, new JpegEncoder { Quality = jpeg_quality }); + await image.SaveAsJpegAsync(stream, new JpegEncoder { Quality = jpeg_quality }); break; default: From 8245bb85dc93970e584d8b821769fb8eda7b7c5f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Dec 2020 18:06:55 +0900 Subject: [PATCH 378/394] Invoke on initial bind --- osu.Game/Screens/Multi/Components/OverlinedPlaylistHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Components/OverlinedPlaylistHeader.cs b/osu.Game/Screens/Multi/Components/OverlinedPlaylistHeader.cs index 02dbe501b1..5552c1cb72 100644 --- a/osu.Game/Screens/Multi/Components/OverlinedPlaylistHeader.cs +++ b/osu.Game/Screens/Multi/Components/OverlinedPlaylistHeader.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Multi.Components { base.LoadComplete(); - Playlist.BindCollectionChanged((_, __) => Details.Value = Playlist.GetTotalDuration()); + Playlist.BindCollectionChanged((_, __) => Details.Value = Playlist.GetTotalDuration(), true); } } } From be456f9c6be31ea98077263f63d84589009429d0 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 3 Dec 2020 18:45:38 +0900 Subject: [PATCH 379/394] Make DroppedObjectAnimation private --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 6d5d25243d..de2782fa35 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -552,11 +552,11 @@ namespace osu.Game.Rulesets.Catch.UI d.Expire(); } - } - public enum DroppedObjectAnimation - { - Drop, - Explode + private enum DroppedObjectAnimation + { + Drop, + Explode + } } } From 7e66714c2fb50c7dd7527d55e5396fe496809139 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 3 Dec 2020 18:45:10 +0900 Subject: [PATCH 380/394] Use ApplyCustomUpdateState for dropping transformation We cannot just apply the transforms because DHO clears transforms when state is updated --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 46 +++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index de2782fa35..1101e5b6b4 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -517,40 +517,40 @@ namespace osu.Game.Rulesets.Catch.UI drop(caughtObject, animation); } - private void drop(Drawable d, DroppedObjectAnimation animation) + private void drop(DrawablePalpableCatchHitObject d, DroppedObjectAnimation animation) { var originalX = d.X * Scale.X; + var startTime = Clock.CurrentTime; d.Anchor = Anchor.TopLeft; d.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(d.DrawPosition, droppedObjectTarget); - animate(d, animation, originalX); + // we cannot just apply the transforms because DHO clears transforms when state is updated + d.ApplyCustomUpdateState += (o, state) => animate(o, animation, originalX, startTime); + if (d.IsLoaded) + animate(d, animation, originalX, startTime); } - private void animate(Drawable d, DroppedObjectAnimation animation, float originalX) + private void animate(Drawable d, DroppedObjectAnimation animation, float originalX, double startTime) { - // temporary hack to make sure transforms are not cleared by DHO state update - if (!d.IsLoaded) + using (d.BeginAbsoluteSequence(startTime)) { - d.OnLoadComplete += _ => animate(d, animation, originalX); - return; + switch (animation) + { + case DroppedObjectAnimation.Drop: + d.MoveToY(d.Y + 75, 750, Easing.InSine); + d.FadeOut(750); + break; + + case DroppedObjectAnimation.Explode: + d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine); + d.MoveToX(d.X + originalX * 6, 1000); + d.FadeOut(750); + break; + } + + d.Expire(); } - - switch (animation) - { - case DroppedObjectAnimation.Drop: - d.MoveToY(d.Y + 75, 750, Easing.InSine); - d.FadeOut(750); - break; - - case DroppedObjectAnimation.Explode: - d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine); - d.MoveToX(d.X + originalX * 6, 1000); - d.FadeOut(750); - break; - } - - d.Expire(); } private enum DroppedObjectAnimation From 73e99718bc23f52854bbc0eb467dcb63bae10a97 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Dec 2020 19:46:42 +0900 Subject: [PATCH 381/394] Change order of OnParentReceived() --- .../Objects/Drawables/DrawableSliderHead.cs | 5 +++++ .../Objects/Drawables/DrawableSliderRepeat.cs | 5 +++++ .../Objects/Drawables/DrawableSliderTick.cs | 10 +++++++++- .../Objects/Drawables/DrawableSpinnerTick.cs | 1 + .../Objects/Drawables/DrawableHitObject.cs | 16 ++++++++++++---- osu.Game/Rulesets/UI/HitObjectContainer.cs | 2 +- osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs | 3 ++- osu.Game/Rulesets/UI/Playfield.cs | 4 +++- 8 files changed, 38 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 3a92938d75..d1928bd4bb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -47,6 +47,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.OnParentReceived(parent); drawableSlider = (DrawableSlider)parent; + } + + protected override void OnApply() + { + base.OnApply(); pathVersion.BindTo(drawableSlider.PathVersion); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 0735d48ae1..f368615e77 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -65,6 +65,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.OnParentReceived(parent); drawableSlider = (DrawableSlider)parent; + } + + protected override void OnApply() + { + base.OnApply(); Position = HitObject.Position - drawableSlider.Position; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index faccf5d4d1..d40b6aea6e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override bool DisplayResult => false; private SkinnableDrawable scaleContainer; + private DrawableSlider drawableSlider; public DrawableSliderTick() : base(null) @@ -66,7 +67,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.OnParentReceived(parent); - Position = HitObject.Position - ((DrawableSlider)parent).HitObject.Position; + drawableSlider = (DrawableSlider)parent; + } + + protected override void OnApply() + { + base.OnApply(); + + Position = HitObject.Position - drawableSlider.HitObject.Position; } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index f37d933e11..d10c4f7511 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void OnParentReceived(DrawableHitObject parent) { base.OnParentReceived(parent); + drawableSpinner = (DrawableSpinner)parent; } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index a922da0aa9..37c36ace7b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -230,12 +230,12 @@ namespace osu.Game.Rulesets.Objects.Drawables foreach (var h in HitObject.NestedHitObjects) { - var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h); + var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h, this); var drawableNested = pooledDrawableNested ?? CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); - // Invoke the event only if this nested object is just created by `CreateNestedHitObject`. + // Only invoke the event for non-pooled DHOs, otherwise the event will be fired by the playfield. if (pooledDrawableNested == null) OnNestedDrawableCreated?.Invoke(drawableNested); @@ -243,10 +243,12 @@ namespace osu.Game.Rulesets.Objects.Drawables drawableNested.OnRevertResult += onRevertResult; drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState; + // ApplyParent() should occur before Apply() in all cases, so it's invoked before the nested DHO is added to the hierarchy below, but after its events are initialised. + if (pooledDrawableNested == null) + drawableNested.ApplyParent(this); + nestedHitObjects.Value.Add(drawableNested); AddNestedHitObject(drawableNested); - - drawableNested.OnParentReceived(this); } StartTimeBindable.BindTo(HitObject.StartTimeBindable); @@ -348,6 +350,12 @@ namespace osu.Game.Rulesets.Objects.Drawables { } + /// + /// Applies a parenting to this . + /// + /// The parenting . + public void ApplyParent(DrawableHitObject parent) => OnParentReceived(parent); + /// /// Invoked when this receives a new parenting . /// diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index ac5d281ddc..12e39d4fbf 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI { Debug.Assert(!drawableMap.ContainsKey(entry)); - var drawable = pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject); + var drawable = pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null); if (drawable == null) throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}."); diff --git a/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs b/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs index 315926dfc6..2d700076d6 100644 --- a/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs +++ b/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs @@ -13,8 +13,9 @@ namespace osu.Game.Rulesets.UI /// Attempts to retrieve the poolable representation of a . /// /// The to retrieve the representation of. + /// The parenting , if any. /// The representing , or null if no poolable representation exists. [CanBeNull] - DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject); + DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject, [CanBeNull] DrawableHitObject parent); } } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index a2ac234471..01b25c9717 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -323,7 +323,7 @@ namespace osu.Game.Rulesets.UI AddInternal(pool); } - DrawableHitObject IPooledHitObjectProvider.GetPooledDrawableRepresentation(HitObject hitObject) + DrawableHitObject IPooledHitObjectProvider.GetPooledDrawableRepresentation(HitObject hitObject, DrawableHitObject parent) { var lookupType = hitObject.GetType(); @@ -359,6 +359,8 @@ namespace osu.Game.Rulesets.UI if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry)) lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject); + if (parent != null) + dho.ApplyParent(parent); dho.Apply(hitObject, entry); }); } From 0bdf99b97a960cd9a5c58e72b4bf3bb0af984ea3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Dec 2020 20:03:39 +0900 Subject: [PATCH 382/394] Remove OnParentReceived() --- .../Objects/Drawables/DrawableSliderHead.cs | 22 +++++---------- .../Objects/Drawables/DrawableSliderRepeat.cs | 17 ++++-------- .../Objects/Drawables/DrawableSliderTick.cs | 12 +++------ .../Objects/Drawables/DrawableSpinnerTick.cs | 15 +++-------- .../Objects/Drawables/DrawableHitObject.cs | 27 +++++++------------ osu.Game/Rulesets/UI/Playfield.cs | 3 +-- 6 files changed, 29 insertions(+), 67 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index d1928bd4bb..f584c9c2d3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -4,20 +4,19 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderHead : DrawableHitCircle { + protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; + private readonly IBindable pathVersion = new Bindable(); protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle; - private DrawableSlider drawableSlider; - - private Slider slider => drawableSlider?.HitObject; + private Slider slider => DrawableSlider?.HitObject; public DrawableSliderHead() { @@ -39,24 +38,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.OnFree(); - pathVersion.UnbindFrom(drawableSlider.PathVersion); - } - - protected override void OnParentReceived(DrawableHitObject parent) - { - base.OnParentReceived(parent); - - drawableSlider = (DrawableSlider)parent; + pathVersion.UnbindFrom(DrawableSlider.PathVersion); } protected override void OnApply() { base.OnApply(); - pathVersion.BindTo(drawableSlider.PathVersion); + pathVersion.BindTo(DrawableSlider.PathVersion); - OnShake = drawableSlider.Shake; - CheckHittable = (d, t) => drawableSlider.CheckHittable?.Invoke(d, t) ?? true; + OnShake = DrawableSlider.Shake; + CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? true; } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index f368615e77..2fd9af894d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public new SliderRepeat HitObject => (SliderRepeat)base.HitObject; + protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; + private double animDuration; public Drawable CirclePiece { get; private set; } @@ -26,8 +28,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override bool DisplayResult => false; - private DrawableSlider drawableSlider; - public DrawableSliderRepeat() : base(null) { @@ -60,24 +60,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); } - protected override void OnParentReceived(DrawableHitObject parent) - { - base.OnParentReceived(parent); - - drawableSlider = (DrawableSlider)parent; - } - protected override void OnApply() { base.OnApply(); - Position = HitObject.Position - drawableSlider.Position; + Position = HitObject.Position - DrawableSlider.Position; } protected override void CheckForResult(bool userTriggered, double timeOffset) { if (HitObject.StartTime <= Time.Current) - ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? r.Judgement.MaxResult : r.Judgement.MinResult); + ApplyResult(r => r.Type = DrawableSlider.Tracking.Value ? r.Judgement.MaxResult : r.Judgement.MinResult); } protected override void UpdateInitialTransforms() @@ -119,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (IsHit) return; bool isRepeatAtEnd = HitObject.RepeatIndex % 2 == 0; - List curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; + List curve = ((PlaySliderBody)DrawableSlider.Body.Drawable).CurrentCurve; Position = isRepeatAtEnd ? end : start; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index d40b6aea6e..c7bfdb02fb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -22,8 +22,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override bool DisplayResult => false; + protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; + private SkinnableDrawable scaleContainer; - private DrawableSlider drawableSlider; public DrawableSliderTick() : base(null) @@ -63,18 +64,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); } - protected override void OnParentReceived(DrawableHitObject parent) - { - base.OnParentReceived(parent); - - drawableSlider = (DrawableSlider)parent; - } - protected override void OnApply() { base.OnApply(); - Position = HitObject.Position - drawableSlider.HitObject.Position; + Position = HitObject.Position - DrawableSlider.HitObject.Position; } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index d10c4f7511..726fbd3ea6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -1,14 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Rulesets.Objects.Drawables; - namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSpinnerTick : DrawableOsuHitObject { public override bool DisplayResult => false; + protected DrawableSpinner DrawableSpinner => (DrawableSpinner)ParentHitObject; + public DrawableSpinnerTick() : base(null) { @@ -19,16 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { } - private DrawableSpinner drawableSpinner; - - protected override void OnParentReceived(DrawableHitObject parent) - { - base.OnParentReceived(parent); - - drawableSpinner = (DrawableSpinner)parent; - } - - protected override double MaximumJudgementOffset => drawableSpinner.HitObject.Duration; + protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration; /// /// Apply a judgement result. diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 37c36ace7b..94d63e4e68 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -43,6 +43,12 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public HitObject HitObject { get; private set; } + /// + /// The parenting , if any. + /// + [CanBeNull] + protected internal DrawableHitObject ParentHitObject { get; internal set; } + /// /// The colour used for various elements of this DrawableHitObject. /// @@ -243,9 +249,9 @@ namespace osu.Game.Rulesets.Objects.Drawables drawableNested.OnRevertResult += onRevertResult; drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState; - // ApplyParent() should occur before Apply() in all cases, so it's invoked before the nested DHO is added to the hierarchy below, but after its events are initialised. - if (pooledDrawableNested == null) - drawableNested.ApplyParent(this); + // This is only necessary for non-pooled DHOs. For pooled DHOs, this is handled inside GetPooledDrawableRepresentation(). + // Must be done before the nested DHO is added to occur before the nested Apply()! + drawableNested.ParentHitObject = this; nestedHitObjects.Value.Add(drawableNested); AddNestedHitObject(drawableNested); @@ -317,6 +323,7 @@ namespace osu.Game.Rulesets.Objects.Drawables OnFree(); HitObject = null; + ParentHitObject = null; Result = null; lifetimeEntry = null; @@ -350,20 +357,6 @@ namespace osu.Game.Rulesets.Objects.Drawables { } - /// - /// Applies a parenting to this . - /// - /// The parenting . - public void ApplyParent(DrawableHitObject parent) => OnParentReceived(parent); - - /// - /// Invoked when this receives a new parenting . - /// - /// The parenting . - protected virtual void OnParentReceived(DrawableHitObject parent) - { - } - /// /// Invoked by the base to populate samples, once on initial load and potentially again on any change to the samples collection. /// diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 01b25c9717..cbf3362ea7 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -359,8 +359,7 @@ namespace osu.Game.Rulesets.UI if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry)) lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject); - if (parent != null) - dho.ApplyParent(parent); + dho.ParentHitObject = parent; dho.Apply(hitObject, entry); }); } From c25e2c3dd577e7d5dc1ce3765ac0d6bec40aca21 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 3 Dec 2020 23:13:14 +0200 Subject: [PATCH 383/394] Select recommended beatmap if last selection is filtered --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 7935debac7..bf045ed612 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Select.Carousel protected override CarouselItem GetNextToSelect() { - if (LastSelected == null) + if (LastSelected == null || LastSelected.Filtered.Value) { if (GetRecommendedBeatmap?.Invoke(Children.OfType().Where(b => !b.Filtered.Value).Select(b => b.Beatmap)) is BeatmapInfo recommended) return Children.OfType().First(b => b.Beatmap == recommended); From 0bc591fef2dc5ee21680b0f361bb3388af35a825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Dec 2020 22:38:51 +0100 Subject: [PATCH 384/394] Add failing assertions `GameplayBeatmap` has to be used instead of the normal bindable `Beatmap`, beecause the former uses osu!-specific hitobjects, while the latter returns convert objects (i.e. `ConvertSlider`s). Similarly, the mod has to be fetched from the player instead of the global bindable, as `Player` has its own cloned instance of the mod, to which the beatmap is applied. The global bindable instance does not have `FirstObject` set. --- .../Mods/TestSceneOsuModHidden.cs | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs index ff308f389f..1ac3ad9194 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -2,12 +2,15 @@ // 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.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods @@ -17,15 +20,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods [Test] public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData { - Mod = new OsuModHidden(), + Mod = new TestOsuModHidden(), Autoplay = true, - PassCondition = checkSomeHit + PassCondition = () => checkSomeHit() && objectWithIncreasedVisibilityHasIndex(0) }); [Test] public void FirstCircleAfterTwoSpinners() => CreateModTest(new ModTestData { - Mod = new OsuModHidden(), + Mod = new TestOsuModHidden(), Autoplay = true, Beatmap = new Beatmap { @@ -54,13 +57,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } } }, - PassCondition = checkSomeHit + PassCondition = () => checkSomeHit() && objectWithIncreasedVisibilityHasIndex(2) }); [Test] public void FirstSliderAfterTwoSpinners() => CreateModTest(new ModTestData { - Mod = new OsuModHidden(), + Mod = new TestOsuModHidden(), Autoplay = true, Beatmap = new Beatmap { @@ -89,13 +92,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } } }, - PassCondition = checkSomeHit + PassCondition = () => checkSomeHit() && objectWithIncreasedVisibilityHasIndex(2) }); [Test] public void TestWithSliderReuse() => CreateModTest(new ModTestData { - Mod = new OsuModHidden(), + Mod = new TestOsuModHidden(), Autoplay = true, Beatmap = new Beatmap { @@ -116,9 +119,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods PassCondition = checkSomeHit }); - private bool checkSomeHit() + private bool checkSomeHit() => Player.ScoreProcessor.JudgedHits >= 4; + + private bool objectWithIncreasedVisibilityHasIndex(int index) + => Player.Mods.Value.OfType().Single().FirstObject == Player.ChildrenOfType().Single().HitObjects[index]; + + private class TestOsuModHidden : OsuModHidden { - return Player.ScoreProcessor.JudgedHits >= 4; + public new HitObject FirstObject => base.FirstObject; } } } From 4d739f11a8352ed16cd7dbb2f499cee9ad6d6240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Dec 2020 22:40:30 +0100 Subject: [PATCH 385/394] Fix spinner ticks getting increased visibility state Regressed in #10696. The old `IsFirstHideableObject()` method did not consider nested hitobjects, while its replacement - `IsFirstAdjustableObject()` - did. Therefore, spinner ticks could be considered first adjustable objects, breaking the old logic. There is no need to match over `SpinnerBonusTick`, as it inherits from `SpinnerTick`. --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 78e759f0e0..45f314af7b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; - protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner); + protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick); public override void ApplyToBeatmap(IBeatmap beatmap) { From 71fa0da7f4dc1b57700e68277e4fed251b22df71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Dec 2020 23:13:48 +0100 Subject: [PATCH 386/394] Add failing test cases --- .../Audio/SampleInfoEqualityTest.cs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 osu.Game.Tests/Audio/SampleInfoEqualityTest.cs diff --git a/osu.Game.Tests/Audio/SampleInfoEqualityTest.cs b/osu.Game.Tests/Audio/SampleInfoEqualityTest.cs new file mode 100644 index 0000000000..149096608f --- /dev/null +++ b/osu.Game.Tests/Audio/SampleInfoEqualityTest.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Audio; + +namespace osu.Game.Tests.Audio +{ + [TestFixture] + public class SampleInfoEqualityTest + { + [Test] + public void TestSameSingleSamplesAreEqual() + { + var first = new SampleInfo("sample"); + var second = new SampleInfo("sample"); + + assertEquality(first, second); + } + + [Test] + public void TestDifferentSingleSamplesAreNotEqual() + { + var first = new SampleInfo("first"); + var second = new SampleInfo("second"); + + assertNonEquality(first, second); + } + + [Test] + public void TestDifferentCountSampleSetsAreNotEqual() + { + var first = new SampleInfo("sample", "extra"); + var second = new SampleInfo("sample"); + + assertNonEquality(first, second); + } + + [Test] + public void TestDifferentSampleSetsOfSameCountAreNotEqual() + { + var first = new SampleInfo("first", "common"); + var second = new SampleInfo("common", "second"); + + assertNonEquality(first, second); + } + + [Test] + public void TestSameOrderSameSampleSetsAreEqual() + { + var first = new SampleInfo("first", "second"); + var second = new SampleInfo("first", "second"); + + assertEquality(first, second); + } + + [Test] + public void TestDifferentOrderSameSampleSetsAreEqual() + { + var first = new SampleInfo("first", "second"); + var second = new SampleInfo("second", "first"); + + assertEquality(first, second); + } + + private void assertEquality(SampleInfo first, SampleInfo second) + { + Assert.That(first.Equals(second), Is.True); + Assert.That(first.GetHashCode(), Is.EqualTo(second.GetHashCode())); + } + + private void assertNonEquality(SampleInfo first, SampleInfo second) + { + Assert.That(first.Equals(second), Is.False); + Assert.That(first.GetHashCode(), Is.Not.EqualTo(second.GetHashCode())); + } + } +} From 15d9147eddb9a39a4569d53042fcc05f1d5cf62b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Dec 2020 23:19:26 +0100 Subject: [PATCH 387/394] Ensure equality member consistency for SampleInfo The previous implementation of `SampleInfo`'s equality members was not completely correct in its treatment of the `sampleNames` array. While `Equals()` compared the values of `sampleNames` using `SequenceEqual()`, therefore performing a structural check that inspects the contents of both arrays, `GetHashCode()` used `HashCode.Combine()` directly on the arrays, therefore operating on reference equality. This could cause the pooling mechanism of samples to fail, as pointed out in #11079. To resolve, change the `GetHashCode()` implementation such that it also considers the contents of the array rather than just the reference to the array itself. This is achieved by leveraging `StructuralEqualityComparer`. Additionally, as a bonus, an array sort was added to the constructor of `SampleInfo`. This is intended to be a "canonicalisation" processing step for the array of sample names. Thanks to that sort, two instances of `SampleInfo` that have the same sample names but permutated will also turn out to be equal and have the same hash codes, given the implementation of both equality members. This gives `SampleInfo` set-like semantics. --- osu.Game/Audio/SampleInfo.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 221bc31639..5d8240204e 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections; using System.Collections.Generic; using System.Linq; @@ -17,6 +18,7 @@ namespace osu.Game.Audio public SampleInfo(params string[] sampleNames) { this.sampleNames = sampleNames; + Array.Sort(sampleNames); } public IEnumerable LookupNames => sampleNames; @@ -25,7 +27,9 @@ namespace osu.Game.Audio public override int GetHashCode() { - return HashCode.Combine(sampleNames, Volume); + return HashCode.Combine( + StructuralComparisons.StructuralEqualityComparer.GetHashCode(sampleNames), + Volume); } public bool Equals(SampleInfo other) From 3de46d0a3be4309fb45b58fa1b9d734d8d6a0b55 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Dec 2020 10:09:07 +0900 Subject: [PATCH 388/394] Fix & clarify catcher tests --- .../TestSceneCatcher.cs | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index f41a16026a..194a12a9b7 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests } [Test] - public void TestCatcherStateFruit() + public void TestFruitChangesCatcherState() { AddStep("miss fruit", () => attemptCatch(new Fruit { X = 100 })); checkState(CatcherAnimationState.Fail); @@ -82,7 +82,19 @@ namespace osu.Game.Rulesets.Catch.Tests } [Test] - public void TestCatcherStateTinyDroplet() + public void TestNormalFruitResetsHyperDashState() + { + AddStep("catch hyper fruit", () => attemptCatch(new Fruit + { + HyperDashTarget = new Fruit { X = 100 } + })); + checkHyperDash(true); + AddStep("catch normal fruit", () => attemptCatch(new Fruit())); + checkHyperDash(false); + } + + [Test] + public void TestTinyDropletMissPreservesCatcherState() { AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit { @@ -90,19 +102,21 @@ namespace osu.Game.Rulesets.Catch.Tests })); AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet())); AddStep("miss tiny droplet", () => attemptCatch(new TinyDroplet { X = 100 })); + // catcher state and hyper dash state is preserved checkState(CatcherAnimationState.Kiai); checkHyperDash(true); } [Test] - public void TestCatcherStateBanana() + public void TestBananaMissPreservesCatcherState() { AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit { HyperDashTarget = new Fruit { X = 100 } })); - AddStep("miss banana", () => attemptCatch(new Banana())); - checkState(CatcherAnimationState.Idle); + AddStep("miss banana", () => attemptCatch(new Banana { X = 100 })); + // catcher state is preserved but hyper dash state is reset + checkState(CatcherAnimationState.Kiai); checkHyperDash(false); } @@ -135,18 +149,6 @@ namespace osu.Game.Rulesets.Catch.Tests AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10); } - [Test] - public void TestHyperFruitHyperDash() - { - AddStep("catch hyper fruit", () => attemptCatch(new Fruit - { - HyperDashTarget = new Fruit { X = 100 } - })); - checkHyperDash(true); - AddStep("catch normal fruit", () => attemptCatch(new Fruit())); - checkHyperDash(false); - } - [TestCase(true)] [TestCase(false)] public void TestHitLighting(bool enabled) From e82ca66d3ee144b5569e646d25a22e246458aac8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Dec 2020 10:21:54 +0900 Subject: [PATCH 389/394] Fix depth of dropped objects --- .../TestSceneCatcherArea.cs | 26 +++++++++++++------ .../TestSceneHyperDashColouring.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 6 ++--- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 +-- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 7be6fc92ac..281ddc7eaa 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -72,14 +73,23 @@ namespace osu.Game.Rulesets.Catch.Tests { circleSize = size; - SetContents(() => new CatchInputManager(catchRuleset) + SetContents(() => { - RelativeSizeAxes = Axes.Both, - Child = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size }) + var droppedObjectContainer = new Container(); + + return new CatchInputManager(catchRuleset) { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - }, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + droppedObjectContainer, + new TestCatcherArea(droppedObjectContainer, new BeatmapDifficulty { CircleSize = size }) + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + } + } + }; }); } @@ -91,8 +101,8 @@ namespace osu.Game.Rulesets.Catch.Tests private class TestCatcherArea : CatcherArea { - public TestCatcherArea(BeatmapDifficulty beatmapDifficulty) - : base(beatmapDifficulty) + public TestCatcherArea(Container droppedObjectContainer, BeatmapDifficulty beatmapDifficulty) + : base(droppedObjectContainer, beatmapDifficulty) { } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 1b8368794c..07cb73e5ff 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("create hyper-dashing catcher", () => { - Child = setupSkinHierarchy(catcherArea = new CatcherArea + Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 6934dcc1f9..df87359ed6 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -36,12 +36,12 @@ namespace osu.Game.Rulesets.Catch.UI public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation) { - var explodingFruitContainer = new Container + var droppedObjectContainer = new Container { RelativeSizeAxes = Axes.Both, }; - CatcherArea = new CatcherArea(difficulty) + CatcherArea = new CatcherArea(droppedObjectContainer, difficulty) { Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.UI InternalChildren = new[] { - explodingFruitContainer, + droppedObjectContainer, CatcherArea.MovableCatcher.CreateProxiedContent(), HitObjectContainer, CatcherArea, diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 9cd0785b85..539776354c 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI public readonly Catcher MovableCatcher; private readonly CatchComboDisplay comboDisplay; - public CatcherArea(BeatmapDifficulty difficulty = null) + public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null) { Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); Children = new Drawable[] @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI Margin = new MarginPadding { Bottom = 350f }, X = CatchPlayfield.CENTER_X }, - MovableCatcher = new Catcher(this, this, difficulty) { X = CatchPlayfield.CENTER_X }, + MovableCatcher = new Catcher(this, droppedObjectContainer, difficulty) { X = CatchPlayfield.CENTER_X }, }; } From 23af70dd328c9216dde5540fa8f6c9e6e30c6f8a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Dec 2020 10:24:25 +0900 Subject: [PATCH 390/394] Invert `if` --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 41 +++++++++++++-------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 1101e5b6b4..1037678734 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -245,30 +245,29 @@ namespace osu.Game.Rulesets.Catch.UI catchObjectPosition >= catcherPosition - halfCatchWidth && catchObjectPosition <= catcherPosition + halfCatchWidth; - // droplet doesn't affect the catcher state - if (!(fruit is TinyDroplet)) - { - if (validCatch && fruit.HyperDash) - { - var target = fruit.HyperDashTarget; - var timeDifference = target.StartTime - fruit.StartTime; - double positionDifference = target.X - catcherPosition; - var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0); - - SetHyperDashState(Math.Abs(velocity), target.X); - } - else - SetHyperDashState(); - - if (validCatch) - updateState(fruit.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle); - else if (!(fruit is Banana)) - updateState(CatcherAnimationState.Fail); - } - if (validCatch) placeCaughtObject(fruit); + // droplet doesn't affect the catcher state + if (fruit is TinyDroplet) return validCatch; + + if (validCatch && fruit.HyperDash) + { + var target = fruit.HyperDashTarget; + var timeDifference = target.StartTime - fruit.StartTime; + double positionDifference = target.X - catcherPosition; + var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0); + + SetHyperDashState(Math.Abs(velocity), target.X); + } + else + SetHyperDashState(); + + if (validCatch) + updateState(fruit.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle); + else if (!(fruit is Banana)) + updateState(CatcherAnimationState.Fail); + return validCatch; } From 898802340787ec59b212d8d0e7aa955ed797da88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Dec 2020 14:35:56 +0900 Subject: [PATCH 391/394] Tidy up code formatting and remove unnecessarily publicly exposed methods --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 84 +++++++++++++-------------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 1037678734..2b88f24348 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -175,55 +175,19 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Calculates the scale of the catcher based off the provided beatmap difficulty. /// - private static Vector2 calculateScale(BeatmapDifficulty difficulty) - => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5); + private static Vector2 calculateScale(BeatmapDifficulty difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5); /// /// Calculates the width of the area used for attempting catches in gameplay. /// /// The scale of the catcher. - internal static float CalculateCatchWidth(Vector2 scale) - => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE; + internal static float CalculateCatchWidth(Vector2 scale) => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE; /// /// Calculates the width of the area used for attempting catches in gameplay. /// /// The beatmap difficulty. - internal static float CalculateCatchWidth(BeatmapDifficulty difficulty) - => CalculateCatchWidth(calculateScale(difficulty)); - - /// - /// Add a caught fruit to the catcher's stack. - /// - /// The fruit that was caught. - public void PlaceOnPlate(DrawablePalpableCatchHitObject fruit) - { - var ourRadius = fruit.DisplayRadius; - float theirRadius = 0; - - const float allowance = 10; - - while (caughtFruitContainer.Any(f => - Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = CatchHitObject.OBJECT_RADIUS / 2)) / (allowance / 2))) - { - var diff = (ourRadius + theirRadius) / allowance; - fruit.X += (RNG.NextSingle() - 0.5f) * diff * 2; - fruit.Y -= RNG.NextSingle() * diff; - } - - fruit.X = Math.Clamp(fruit.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2); - - caughtFruitContainer.Add(fruit); - - if (hitLighting.Value) - { - HitExplosion hitExplosion = hitExplosionPool.Get(); - hitExplosion.X = fruit.X; - hitExplosion.Scale = new Vector2(fruit.HitObject.Scale); - hitExplosion.ObjectColour = fruit.AccentColour.Value; - hitExplosionContainer.Add(hitExplosion); - } - } + internal static float CalculateCatchWidth(BeatmapDifficulty difficulty) => CalculateCatchWidth(calculateScale(difficulty)); /// /// Let the catcher attempt to catch a fruit. @@ -375,12 +339,10 @@ namespace osu.Game.Rulesets.Catch.UI public void Drop() => clearPlate(DroppedObjectAnimation.Drop); /// - /// Explode any fruit off the plate. + /// Explode all fruit off the plate. /// public void Explode() => clearPlate(DroppedObjectAnimation.Explode); - public void Explode(DrawablePalpableCatchHitObject caughtObject) => removeFromPlate(caughtObject, DroppedObjectAnimation.Explode); - protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); @@ -456,6 +418,7 @@ namespace osu.Game.Rulesets.Catch.UI private void placeCaughtObject(PalpableCatchHitObject source) { var caughtObject = createCaughtObject(source); + if (caughtObject == null) return; caughtObject.RelativePositionAxes = Axes.None; @@ -468,10 +431,43 @@ namespace osu.Game.Rulesets.Catch.UI caughtObject.LifetimeStart = source.StartTime; caughtObject.LifetimeEnd = double.MaxValue; - PlaceOnPlate(caughtObject); + adjustPositionInStack(caughtObject); + + caughtFruitContainer.Add(caughtObject); + + addLighting(caughtObject); if (!caughtObject.StaysOnPlate) - Explode(caughtObject); + removeFromPlate(caughtObject, DroppedObjectAnimation.Explode); + } + + private void adjustPositionInStack(DrawablePalpableCatchHitObject caughtObject) + { + const float radius_div_2 = CatchHitObject.OBJECT_RADIUS / 2; + const float allowance = 10; + + float caughtObjectRadius = caughtObject.DisplayRadius; + + while (caughtFruitContainer.Any(f => Vector2Extensions.Distance(f.Position, caughtObject.Position) < (caughtObjectRadius + radius_div_2) / (allowance / 2))) + { + float diff = (caughtObjectRadius + radius_div_2) / allowance; + + caughtObject.X += (RNG.NextSingle() - 0.5f) * diff * 2; + caughtObject.Y -= RNG.NextSingle() * diff; + } + + caughtObject.X = Math.Clamp(caughtObject.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2); + } + + private void addLighting(DrawablePalpableCatchHitObject caughtObject) + { + if (!hitLighting.Value) return; + + HitExplosion hitExplosion = hitExplosionPool.Get(); + hitExplosion.X = caughtObject.X; + hitExplosion.Scale = new Vector2(caughtObject.HitObject.Scale); + hitExplosion.ObjectColour = caughtObject.AccentColour.Value; + hitExplosionContainer.Add(hitExplosion); } private DrawablePalpableCatchHitObject createCaughtObject(PalpableCatchHitObject source) From d3a17b65d52f1eabb6351a9036e983d1970ac5bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Dec 2020 14:36:40 +0900 Subject: [PATCH 392/394] Move public methods upwards --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 50 +++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 2b88f24348..2a3447c80a 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -266,24 +266,17 @@ namespace osu.Game.Rulesets.Catch.UI } } - private void runHyperDashStateTransition(bool hyperDashing) + public void UpdatePosition(float position) { - updateTrailVisibility(); + position = Math.Clamp(position, 0, CatchPlayfield.WIDTH); - if (hyperDashing) - { - this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); - this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); - } - else - { - this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); - this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); - } + if (position == X) + return; + + Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y); + X = position; } - private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing; - public bool OnPressed(CatchAction action) { switch (action) @@ -322,17 +315,6 @@ namespace osu.Game.Rulesets.Catch.UI } } - public void UpdatePosition(float position) - { - position = Math.Clamp(position, 0, CatchPlayfield.WIDTH); - - if (position == X) - return; - - Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y); - X = position; - } - /// /// Drop any fruit off the plate. /// @@ -343,6 +325,24 @@ namespace osu.Game.Rulesets.Catch.UI /// public void Explode() => clearPlate(DroppedObjectAnimation.Explode); + private void runHyperDashStateTransition(bool hyperDashing) + { + updateTrailVisibility(); + + if (hyperDashing) + { + this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); + this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); + } + else + { + this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); + this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); + } + } + + private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing; + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); From b8c284b34fcfd754451382ab66612c3de5c5f37e Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 4 Dec 2020 00:51:46 -0800 Subject: [PATCH 393/394] Fix one more key binding string not being sentence cased --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index f4a4813b94..1270df5374 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -149,7 +149,7 @@ namespace osu.Game.Input.Bindings [Description("Select")] Select, - [Description("Quick exit (Hold)")] + [Description("Quick exit (hold)")] QuickExit, // Game-wide beatmap music controller keybindings From 7c0edb796ee352cbc96950e152d7308dade7e1f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Dec 2020 20:49:18 +0900 Subject: [PATCH 394/394] Always confine mouse to screen when running fullscreen --- osu.Game/Input/ConfineMouseTracker.cs | 15 +++++++++++---- osu.Game/Input/OsuConfineMouseMode.cs | 5 ----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index 3dadae6317..75d9c8debb 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -18,6 +18,8 @@ namespace osu.Game.Input public class ConfineMouseTracker : Component { private Bindable frameworkConfineMode; + private Bindable frameworkWindowMode; + private Bindable osuConfineMode; private IBindable localUserPlaying; @@ -25,6 +27,9 @@ namespace osu.Game.Input private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) { frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode); + frameworkWindowMode = frameworkConfigManager.GetBindable(FrameworkSetting.WindowMode); + frameworkWindowMode.BindValueChanged(_ => updateConfineMode()); + osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); @@ -38,16 +43,18 @@ namespace osu.Game.Input if (frameworkConfineMode.Disabled) return; + if (frameworkWindowMode.Value == WindowMode.Fullscreen) + { + frameworkConfineMode.Value = ConfineMouseMode.Fullscreen; + return; + } + switch (osuConfineMode.Value) { case OsuConfineMouseMode.Never: frameworkConfineMode.Value = ConfineMouseMode.Never; break; - case OsuConfineMouseMode.Fullscreen: - frameworkConfineMode.Value = ConfineMouseMode.Fullscreen; - break; - case OsuConfineMouseMode.DuringGameplay: frameworkConfineMode.Value = localUserPlaying.Value ? ConfineMouseMode.Always : ConfineMouseMode.Never; break; diff --git a/osu.Game/Input/OsuConfineMouseMode.cs b/osu.Game/Input/OsuConfineMouseMode.cs index 32b456395c..a4a1c9eb46 100644 --- a/osu.Game/Input/OsuConfineMouseMode.cs +++ b/osu.Game/Input/OsuConfineMouseMode.cs @@ -17,11 +17,6 @@ namespace osu.Game.Input /// Never, - /// - /// The mouse cursor will be locked to the window bounds while in fullscreen mode. - /// - Fullscreen, - /// /// The mouse cursor will be locked to the window bounds during gameplay, /// but may otherwise move freely.