From 2071cba944050ce4da997b1e8111d96650d12c22 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 13 Nov 2020 12:32:23 -0800 Subject: [PATCH 001/198] 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/198] 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/198] 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/198] 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/198] 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/198] 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/198] 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/198] 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/198] 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/198] 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/198] 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/198] 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/198] 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/198] 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/198] 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 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 016/198] 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 017/198] 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 018/198] 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 019/198] 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 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 020/198] 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 021/198] 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 8be31f4805385708909b4405dd3c2aeb427bda15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 23:55:21 +0900 Subject: [PATCH 022/198] 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 023/198] 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 024/198] 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 025/198] 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 026/198] 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 027/198] 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 028/198] 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 029/198] 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 030/198] 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 031/198] 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 032/198] 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 033/198] 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 034/198] 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 035/198] 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 036/198] 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 85c5c68dfac097bc15c9218853de000816b74f58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 13:20:21 +0900 Subject: [PATCH 037/198] 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 038/198] 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 039/198] 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 040/198] 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 041/198] 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 042/198] 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 043/198] 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 044/198] 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 045/198] 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 046/198] 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 047/198] 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 048/198] 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 049/198] 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 d467a00eeac6f04e6b192c7513538b22e3d1f0d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 00:11:31 +0900 Subject: [PATCH 050/198] 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 051/198] 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 052/198] 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 053/198] 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 054/198] 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 055/198] 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 056/198] 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 057/198] 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 058/198] 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 059/198] 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 060/198] 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 061/198] 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 062/198] 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 063/198] 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 064/198] 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 065/198] 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 066/198] 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 067/198] 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 068/198] 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 069/198] 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 070/198] 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 071/198] 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 072/198] 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 073/198] 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 074/198] 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 075/198] 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 076/198] 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 077/198] 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 078/198] 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 079/198] 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 080/198] 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 081/198] 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 082/198] 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 083/198] 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 084/198] 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 085/198] 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 086/198] 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 087/198] 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 088/198] 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 089/198] 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 090/198] 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 091/198] 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 092/198] 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 093/198] 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 094/198] 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 095/198] 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 096/198] 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 097/198] 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 098/198] 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 099/198] 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 100/198] 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 101/198] 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 102/198] 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 103/198] 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 104/198] 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 105/198] 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 106/198] 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 107/198] 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 108/198] 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 109/198] 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 110/198] 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 111/198] 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 112/198] 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 113/198] 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 114/198] 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 115/198] 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 116/198] 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 117/198] 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 118/198] 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 119/198] 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 120/198] 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 121/198] 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 122/198] 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 123/198] 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 124/198] 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 125/198] 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 126/198] 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 127/198] 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 128/198] 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 129/198] 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 130/198] 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 131/198] 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 132/198] 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 133/198] 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 134/198] 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 135/198] 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 136/198] 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 137/198] 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 138/198] 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 916a313f1965ae52872001bd6b3e511854d3368d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Nov 2020 19:13:46 +0900 Subject: [PATCH 139/198] 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 140/198] 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 141/198] 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 142/198] 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 143/198] 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 144/198] 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 145/198] 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 146/198] 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 147/198] 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 148/198] 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 149/198] 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 9131546876b33e7a6cf56e18daddea2bbfca089e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 26 Nov 2020 13:04:19 +0900 Subject: [PATCH 150/198] 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 8a73b335f3b59bf84e2b71c7d82ba673a4e78375 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 26 Nov 2020 14:26:38 +0900 Subject: [PATCH 151/198] 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 152/198] 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 153/198] 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 e53f849aa056ac40f5dc9d745a0ad1bc94541e87 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 26 Nov 2020 18:14:25 +0900 Subject: [PATCH 154/198] 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 155/198] 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 156/198] 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 157/198] 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 158/198] 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 159/198] 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 3346c06aca2469288f1aa2a4ed48fc7595c97122 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Nov 2020 20:04:44 +0900 Subject: [PATCH 160/198] 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 161/198] 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 162/198] 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 163/198] 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 164/198] 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 165/198] 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 166/198] 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 167/198] 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 168/198] 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 169/198] 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 170/198] 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 171/198] 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 172/198] 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 792934f2c4ee5617c8a7ca68e8840d3c14400fb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Nov 2020 13:54:36 +0900 Subject: [PATCH 173/198] 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 174/198] 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 175/198] 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 176/198] 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 7edbba58f70d8d61ddfcb6e90d57db0424751f2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Nov 2020 16:28:29 +0900 Subject: [PATCH 177/198] 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 178/198] 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 179/198] 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 180/198] 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 8ad4cf73f5c17fca110486de442a83cf11074473 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 28 Nov 2020 17:07:29 +0200 Subject: [PATCH 181/198] 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 182/198] 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 183/198] 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 184/198] 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 185/198] 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 186/198] 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 5d3a5081a0548f98d485c4dfe8d879e70536159d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 30 Nov 2020 12:52:58 +0900 Subject: [PATCH 187/198] 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 188/198] 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 189/198] 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 190/198] 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 6bea78619a9c59aefcbd47d935233fc61496031e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 30 Nov 2020 13:33:29 +0900 Subject: [PATCH 191/198] 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 192/198] 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 193/198] 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 194/198] 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 195/198] 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 e14db45374a2a60424aad8586edd1851cb0a9040 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Nov 2020 16:09:38 +0900 Subject: [PATCH 196/198] 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 197/198] 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 198/198] 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"; + } } }