From 7874045b1f82e34dac1df8eb8d849f25a878ab97 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 12 May 2020 21:12:48 +0200 Subject: [PATCH 01/39] Allow disabling SkipOverlay through AllowGameplayOverlays. --- osu.Game/Screens/Play/Player.cs | 6 +++++- osu.Game/Screens/Play/SkipOverlay.cs | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a2735c8c55..b24cef8eae 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -83,6 +83,8 @@ namespace osu.Game.Screens.Play private BreakTracker breakTracker; + private SkipOverlay skipOverlay; + protected ScoreProcessor ScoreProcessor { get; private set; } protected HealthProcessor HealthProcessor { get; private set; } @@ -189,6 +191,8 @@ namespace osu.Game.Screens.Play HUDOverlay.ShowHud.Value = false; HUDOverlay.ShowHud.Disabled = true; BreakOverlay.Hide(); + skipOverlay.Disabled = true; + skipOverlay.Hide(); } DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -290,7 +294,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre }, - new SkipOverlay(DrawableRuleset.GameplayStartTime) + skipOverlay = new SkipOverlay(DrawableRuleset.GameplayStartTime) { RequestSkip = GameplayClockContainer.Skip }, diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index ac7e509c2c..757dcd21ed 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -39,6 +39,8 @@ namespace osu.Game.Screens.Play [Resolved] private GameplayClock gameplayClock { get; set; } + public bool Disabled { get; set; } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; /// @@ -105,7 +107,8 @@ namespace osu.Game.Screens.Play button.Action = () => RequestSkip?.Invoke(); displayTime = gameplayClock.CurrentTime; - Show(); + if (!Disabled) + Show(); } protected override void PopIn() => this.FadeIn(fade_time); @@ -121,7 +124,7 @@ namespace osu.Game.Screens.Play remainingTimeBox.Width = (float)Interpolation.Lerp(remainingTimeBox.Width, progress, Math.Clamp(Time.Elapsed / 40, 0, 1)); button.Enabled.Value = progress > 0; - State.Value = progress > 0 ? Visibility.Visible : Visibility.Hidden; + State.Value = progress > 0 && !Disabled ? Visibility.Visible : Visibility.Hidden; } protected override bool OnMouseMove(MouseMoveEvent e) From e390d70b707c97909404b8f596dc2908a66605b4 Mon Sep 17 00:00:00 2001 From: Fukashi13 <48766178+Fukashi13@users.noreply.github.com> Date: Thu, 14 May 2020 14:33:12 +0200 Subject: [PATCH 02/39] bestMatch changes on entering section with screen top border --- osu.Game/Graphics/Containers/SectionsContainer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index a3125614aa..8b866c8d21 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -189,15 +189,15 @@ namespace osu.Game.Graphics.Containers headerBackgroundContainer.Height = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0); headerBackgroundContainer.Y = ExpandableHeader?.Y ?? 0; - T bestMatch = null; - float minDiff = float.MaxValue; + T bestMatch = Children.FirstOrDefault(); + float minDiff = float.MinValue; float scrollOffset = FixedHeader?.LayoutSize.Y ?? 0; foreach (var section in Children) { - float diff = Math.Abs(scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset); + float diff = scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset; - if (diff < minDiff) + if ((minDiff < diff) & (diff < 0)) { minDiff = diff; bestMatch = section; From 097fcfd9ad35832a190a9bee0f7bfd14f33aa146 Mon Sep 17 00:00:00 2001 From: Fukashi13 <48766178+Fukashi13@users.noreply.github.com> Date: Fri, 15 May 2020 00:06:58 +0200 Subject: [PATCH 03/39] Update osu.Game/Graphics/Containers/SectionsContainer.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Graphics/Containers/SectionsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 8b866c8d21..5192a7ddea 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -197,7 +197,7 @@ namespace osu.Game.Graphics.Containers { float diff = scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset; - if ((minDiff < diff) & (diff < 0)) + if (minDiff < diff && diff < 0) { minDiff = diff; bestMatch = section; From c55eb8335135359ee20ee7a3900392b35981b6da Mon Sep 17 00:00:00 2001 From: Fukashi13 <48766178+Fukashi13@users.noreply.github.com> Date: Fri, 15 May 2020 14:03:45 +0200 Subject: [PATCH 04/39] last section gets selected when scrolling to bottom of list --- .../Graphics/Containers/SectionsContainer.cs | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 5192a7ddea..b9ed9b90fc 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -189,23 +189,18 @@ namespace osu.Game.Graphics.Containers headerBackgroundContainer.Height = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0); headerBackgroundContainer.Y = ExpandableHeader?.Y ?? 0; - T bestMatch = Children.FirstOrDefault(); - float minDiff = float.MinValue; float scrollOffset = FixedHeader?.LayoutSize.Y ?? 0; + Func diff = section => scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset; - foreach (var section in Children) + if (scrollContainer.IsScrolledToEnd()) { - float diff = scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset; - - if (minDiff < diff && diff < 0) - { - minDiff = diff; - bestMatch = section; - } + SelectedSection.Value = Children.LastOrDefault(); + } + else + { + SelectedSection.Value = Children.TakeWhile(section => diff(section) <= 0).LastOrDefault() + ?? Children.FirstOrDefault(); } - - if (bestMatch != null) - SelectedSection.Value = bestMatch; } } From 6416ace70d07f8a5d67bfd469ee93e73f1af5266 Mon Sep 17 00:00:00 2001 From: Fukashi13 <48766178+Fukashi13@users.noreply.github.com> Date: Fri, 15 May 2020 14:31:05 +0200 Subject: [PATCH 05/39] fixed indent --- osu.Game/Graphics/Containers/SectionsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index b9ed9b90fc..d739f56828 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -199,7 +199,7 @@ namespace osu.Game.Graphics.Containers else { SelectedSection.Value = Children.TakeWhile(section => diff(section) <= 0).LastOrDefault() - ?? Children.FirstOrDefault(); + ?? Children.FirstOrDefault(); } } } From 4096463d02d8ac096f6e4c7d1d23d89b9575e0c9 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 15 May 2020 19:43:01 +0200 Subject: [PATCH 06/39] Move SkipOverlay internal alpha manipulation to a nested container and adjust visual tests. --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 30 ++++++++++++------- osu.Game/Screens/Play/Player.cs | 1 - osu.Game/Screens/Play/SkipOverlay.cs | 27 +++++++++-------- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 6a0f86fe53..7c4ae4fc52 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -2,9 +2,9 @@ // 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.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneSkipOverlay : OsuManualInputManagerTestScene { - private SkipOverlay skip; + private TestSkipOverlay skip; private int requestCount; private double increment; @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - skip = new SkipOverlay(skip_time) + skip = new TestSkipOverlay(skip_time) { RequestSkip = () => { @@ -56,19 +56,19 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestFadeOnIdle() { AddStep("move mouse", () => InputManager.MoveMouseTo(Vector2.Zero)); - AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1); - AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1); + AddUntilStep("fully visible", () => skip.OverlayContents.Alpha == 1); + AddUntilStep("wait for fade", () => skip.OverlayContents.Alpha < 1); AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1); - AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1); + AddUntilStep("fully visible", () => skip.OverlayContents.Alpha == 1); + AddUntilStep("wait for fade", () => skip.OverlayContents.Alpha < 1); } [Test] public void TestClickableAfterFade() { AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for fade", () => skip.Children.First().Alpha == 0); + AddUntilStep("wait for fade", () => skip.OverlayContents.Alpha == 0); AddStep("click", () => InputManager.Click(MouseButton.Left)); checkRequestCount(1); } @@ -105,13 +105,23 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddStep("button down", () => InputManager.PressButton(MouseButton.Left)); - AddUntilStep("wait for overlay disappear", () => !skip.IsPresent); - AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0); + AddUntilStep("wait for overlay disappear", () => !skip.Child.IsPresent); + AddAssert("ensure button didn't disappear", () => skip.OverlayContents.Alpha > 0); AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left)); checkRequestCount(0); } private void checkRequestCount(int expected) => AddAssert($"request count is {expected}", () => requestCount == expected); + + private class TestSkipOverlay : SkipOverlay + { + public TestSkipOverlay(double startTime) + : base(startTime) + { + } + + public Drawable OverlayContents => (Child as Container).Child; + } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b24cef8eae..02c7b671a3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -191,7 +191,6 @@ namespace osu.Game.Screens.Play HUDOverlay.ShowHud.Value = false; HUDOverlay.ShowHud.Disabled = true; BreakOverlay.Hide(); - skipOverlay.Disabled = true; skipOverlay.Hide(); } diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 757dcd21ed..fec35df4e3 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -24,13 +24,14 @@ using osu.Game.Input.Bindings; namespace osu.Game.Screens.Play { - public class SkipOverlay : VisibilityContainer, IKeyBindingHandler + public class SkipOverlay : Container, IKeyBindingHandler { private readonly double startTime; public Action RequestSkip; private Button button; + private ButtonContainer buttonContainer; private Box remainingTimeBox; private FadeContainer fadeContainer; @@ -39,8 +40,6 @@ namespace osu.Game.Screens.Play [Resolved] private GameplayClock gameplayClock { get; set; } - public bool Disabled { get; set; } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; /// @@ -63,9 +62,10 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader(true)] private void load(OsuColour colours) { - Children = new Drawable[] + Child = buttonContainer = new ButtonContainer { - fadeContainer = new FadeContainer + RelativeSizeAxes = Axes.Both, + Child = fadeContainer = new FadeContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -106,15 +106,8 @@ namespace osu.Game.Screens.Play button.Action = () => RequestSkip?.Invoke(); displayTime = gameplayClock.CurrentTime; - - if (!Disabled) - Show(); } - protected override void PopIn() => this.FadeIn(fade_time); - - protected override void PopOut() => this.FadeOut(fade_time); - protected override void Update() { base.Update(); @@ -124,13 +117,14 @@ namespace osu.Game.Screens.Play remainingTimeBox.Width = (float)Interpolation.Lerp(remainingTimeBox.Width, progress, Math.Clamp(Time.Elapsed / 40, 0, 1)); button.Enabled.Value = progress > 0; - State.Value = progress > 0 && !Disabled ? Visibility.Visible : Visibility.Hidden; + buttonContainer.State.Value = progress > 0 ? Visibility.Visible : Visibility.Hidden; } protected override bool OnMouseMove(MouseMoveEvent e) { if (!e.HasAnyButtonPressed) fadeContainer.Show(); + return base.OnMouseMove(e); } @@ -217,6 +211,13 @@ namespace osu.Game.Screens.Play public override void Show() => State = Visibility.Visible; } + private class ButtonContainer : VisibilityContainer + { + protected override void PopIn() => this.FadeIn(fade_time); + + protected override void PopOut() => this.FadeOut(fade_time); + } + private class Button : OsuClickableContainer { private Color4 colourNormal; From ed9d6f28297c2801e1e6ad87bb81205e8d66c5e0 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 15 May 2020 22:58:15 +0200 Subject: [PATCH 07/39] Fix CI inspection. --- osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 7c4ae4fc52..e093542d1e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Gameplay { } - public Drawable OverlayContents => (Child as Container).Child; + public Drawable OverlayContents => (Child as Container)?.Child; } } } From 76c5be7bc1039611957ac5ba2b781255ae36ee23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 17 May 2020 17:16:22 +0200 Subject: [PATCH 08/39] Disallow catch-specific judgements in mania --- .../Scoring/ManiaHitWindows.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs index 549f0f9214..289f8a00ef 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs @@ -7,5 +7,20 @@ namespace osu.Game.Rulesets.Mania.Scoring { public class ManiaHitWindows : HitWindows { + public override bool IsHitResultAllowed(HitResult result) + { + switch (result) + { + case HitResult.Perfect: + case HitResult.Great: + case HitResult.Good: + case HitResult.Ok: + case HitResult.Meh: + case HitResult.Miss: + return true; + } + + return false; + } } } From 59ef6002bd9e3b32adbd00761456687c36ff312c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 May 2020 13:12:30 +0900 Subject: [PATCH 09/39] 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 650ebde54d..eaad4daf35 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 ee6206e166..9112dfe46e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index cbf8600c62..3f0630af5f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 1865cd0762f6e99bfabba22a76ca2165e0b2c08d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 May 2020 15:10:59 +0900 Subject: [PATCH 10/39] Fix possible exceptions in performance calculators --- .../Difficulty/CatchPerformanceCalculator.cs | 12 ++++++------ .../Difficulty/ManiaPerformanceCalculator.cs | 13 +++++++------ .../Difficulty/OsuPerformanceCalculator.cs | 9 +++++---- .../Difficulty/TaikoPerformanceCalculator.cs | 9 +++++---- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index e7ce680365..2dc28fad35 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -4,12 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; -using osu.Game.Scoring.Legacy; namespace osu.Game.Rulesets.Catch.Difficulty { @@ -34,11 +34,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty { mods = Score.Mods; - fruitsHit = Score?.GetCount300() ?? Score.Statistics[HitResult.Perfect]; - ticksHit = Score?.GetCount100() ?? 0; - tinyTicksHit = Score?.GetCount50() ?? 0; - tinyTicksMissed = Score?.GetCountKatu() ?? 0; - misses = Score.Statistics[HitResult.Miss]; + fruitsHit = Score.Statistics.GetOrDefault(HitResult.Perfect); + ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit); + tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit); + tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss); + misses = Score.Statistics.GetOrDefault(HitResult.Miss); // Don't count scores made with supposedly unranked mods if (mods.Any(m => !m.Ranked)) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 3f7a2baedd..91383c5548 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -37,12 +38,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty { mods = Score.Mods; scaledScore = Score.TotalScore; - countPerfect = Score.Statistics[HitResult.Perfect]; - countGreat = Score.Statistics[HitResult.Great]; - countGood = Score.Statistics[HitResult.Good]; - countOk = Score.Statistics[HitResult.Ok]; - countMeh = Score.Statistics[HitResult.Meh]; - countMiss = Score.Statistics[HitResult.Miss]; + countPerfect = Score.Statistics.GetOrDefault(HitResult.Perfect); + countGreat = Score.Statistics.GetOrDefault(HitResult.Great); + countGood = Score.Statistics.GetOrDefault(HitResult.Good); + countOk = Score.Statistics.GetOrDefault(HitResult.Ok); + countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); + countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); if (mods.Any(m => !m.Ranked)) return 0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index ce8ecf02ac..4022b554f4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -45,10 +46,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty mods = Score.Mods; accuracy = Score.Accuracy; scoreMaxCombo = Score.MaxCombo; - countGreat = Score.Statistics[HitResult.Great]; - countGood = Score.Statistics[HitResult.Good]; - countMeh = Score.Statistics[HitResult.Meh]; - countMiss = Score.Statistics[HitResult.Miss]; + countGreat = Score.Statistics.GetOrDefault(HitResult.Great); + countGood = Score.Statistics.GetOrDefault(HitResult.Good); + countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); + countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); // Don't count scores made with supposedly unranked mods if (mods.Any(m => !m.Ranked)) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 3a0fb64622..bc147b53ac 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -31,10 +32,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty public override double Calculate(Dictionary categoryDifficulty = null) { mods = Score.Mods; - countGreat = Score.Statistics[HitResult.Great]; - countGood = Score.Statistics[HitResult.Good]; - countMeh = Score.Statistics[HitResult.Meh]; - countMiss = Score.Statistics[HitResult.Miss]; + countGreat = Score.Statistics.GetOrDefault(HitResult.Great); + countGood = Score.Statistics.GetOrDefault(HitResult.Good); + countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); + countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); // Don't count scores made with supposedly unranked mods if (mods.Any(m => !m.Ranked)) From b43e9781566e45723dfe33ebc24fc8083edd0f96 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 18 May 2020 17:44:56 +0800 Subject: [PATCH 11/39] Unify to use double in CatchPerformanceCalculator. --- .../Difficulty/CatchPerformanceCalculator.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 2dc28fad35..2ee7cea645 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty // Longer maps are worth more double lengthBonus = - 0.95f + 0.3f * Math.Min(1.0f, numTotalHits / 2500.0f) + - (numTotalHits > 2500 ? (float)Math.Log10(numTotalHits / 2500.0f) * 0.475f : 0.0f); + 0.95 + 0.3 * Math.Min(1.0, numTotalHits / 2500.0) + + (numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0); // Longer maps are worth more value *= lengthBonus; @@ -65,14 +65,14 @@ namespace osu.Game.Rulesets.Catch.Difficulty if (Attributes.MaxCombo > 0) value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); - float approachRate = (float)Attributes.ApproachRate; - float approachRateFactor = 1.0f; - if (approachRate > 9.0f) - approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9 - if (approachRate > 10.0f) - approachRateFactor += 0.1f * (approachRate - 10.0f); // Additional 10% at AR 11, 30% total - else if (approachRate < 8.0f) - approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8 + double approachRate = Attributes.ApproachRate; + double approachRateFactor = 1.0; + if (approachRate > 9.0) + approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9 + if (approachRate > 10.0) + approachRateFactor += 0.1 * (approachRate - 10.0); // Additional 10% at AR 11, 30% total + else if (approachRate < 8.0) + approachRateFactor += 0.025 * (8.0 - approachRate); // 2.5% for each AR below 8 value *= approachRateFactor; @@ -80,10 +80,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty { value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10 // Hiddens gives almost nothing on max approach rate, and more the lower it is - if (approachRate <= 10.0f) - value *= 1.05f + 0.075f * (10.0f - approachRate); // 7.5% for each AR below 10 - else if (approachRate > 10.0f) - value *= 1.01f + 0.04f * (11.0f - Math.Min(11.0f, approachRate)); // 5% at AR 10, 1% at AR 11 + if (approachRate <= 10.0) + value *= 1.05 + 0.075 * (10.0 - approachRate); // 7.5% for each AR below 10 + else if (approachRate > 10.0) + value *= 1.01 + 0.04 * (11.0 - Math.Min(11.0, approachRate)); // 5% at AR 10, 1% at AR 11 } if (mods.Any(m => m is ModFlashlight)) @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty return value; } - private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0, 1); + private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1); private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed; private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit; private int totalComboHits() => misses + ticksHit + fruitsHit; From 373aae06109b845c078dcaaacab8deb5406094b3 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 18 May 2020 17:45:32 +0800 Subject: [PATCH 12/39] Use int for total hits in OsuPerformanceCalculator. --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 4022b554f4..c8c6db06d7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty return accuracyValue; } - private double totalHits => countGreat + countGood + countMeh + countMiss; - private double totalSuccessfulHits => countGreat + countGood + countMeh; + private int totalHits => countGreat + countGood + countMeh + countMiss; + private int totalSuccessfulHits => countGreat + countGood + countMeh; } } From c20902f249883ab6a16ca95d3096463767cc015a Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 18 May 2020 18:22:03 +0800 Subject: [PATCH 13/39] Fix double in accuracy calculation in OsuPerformanceCalculator. --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index c8c6db06d7..6f4c0f9cfa 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty int amountHitObjectsWithAccuracy = countHitCircles; if (amountHitObjectsWithAccuracy > 0) - betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (amountHitObjectsWithAccuracy * 6); + betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); else betterAccuracyPercentage = 0; From 49ee05c3c4dbd8b8d9d6f673fb887665b384b592 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 May 2020 19:37:49 +0900 Subject: [PATCH 14/39] Make into CompositeDrawable --- osu.Game/Screens/Play/SkipOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index fec35df4e3..b123757ded 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -24,7 +24,7 @@ using osu.Game.Input.Bindings; namespace osu.Game.Screens.Play { - public class SkipOverlay : Container, IKeyBindingHandler + public class SkipOverlay : CompositeDrawable, IKeyBindingHandler { private readonly double startTime; @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader(true)] private void load(OsuColour colours) { - Child = buttonContainer = new ButtonContainer + InternalChild = buttonContainer = new ButtonContainer { RelativeSizeAxes = Axes.Both, Child = fadeContainer = new FadeContainer From 2fd25f5ee6776fd9c17f3ae25a1e00a6d435ba42 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 May 2020 19:54:26 +0900 Subject: [PATCH 15/39] Fix tests --- osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index e093542d1e..12ada088a1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddStep("button down", () => InputManager.PressButton(MouseButton.Left)); - AddUntilStep("wait for overlay disappear", () => !skip.Child.IsPresent); + AddUntilStep("wait for overlay disappear", () => !skip.OverlayContents.IsPresent); AddAssert("ensure button didn't disappear", () => skip.OverlayContents.Alpha > 0); AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left)); checkRequestCount(0); @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Gameplay { } - public Drawable OverlayContents => (Child as Container)?.Child; + public Drawable OverlayContents => (InternalChild as Container)?.Child; } } } From f98ee2718552e1663318e7466d70cc51e983264d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 May 2020 20:01:00 +0900 Subject: [PATCH 16/39] Fix referencing wrong child --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 12ada088a1..7ed7a116b4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -56,19 +56,19 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestFadeOnIdle() { AddStep("move mouse", () => InputManager.MoveMouseTo(Vector2.Zero)); - AddUntilStep("fully visible", () => skip.OverlayContents.Alpha == 1); - AddUntilStep("wait for fade", () => skip.OverlayContents.Alpha < 1); + AddUntilStep("fully visible", () => skip.FadingContent.Alpha == 1); + AddUntilStep("wait for fade", () => skip.FadingContent.Alpha < 1); AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("fully visible", () => skip.OverlayContents.Alpha == 1); - AddUntilStep("wait for fade", () => skip.OverlayContents.Alpha < 1); + AddUntilStep("fully visible", () => skip.FadingContent.Alpha == 1); + AddUntilStep("wait for fade", () => skip.FadingContent.Alpha < 1); } [Test] public void TestClickableAfterFade() { AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for fade", () => skip.OverlayContents.Alpha == 0); + AddUntilStep("wait for fade", () => skip.FadingContent.Alpha == 0); AddStep("click", () => InputManager.Click(MouseButton.Left)); checkRequestCount(1); } @@ -105,8 +105,8 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddStep("button down", () => InputManager.PressButton(MouseButton.Left)); - AddUntilStep("wait for overlay disappear", () => !skip.OverlayContents.IsPresent); - AddAssert("ensure button didn't disappear", () => skip.OverlayContents.Alpha > 0); + AddUntilStep("wait for overlay disappear", () => !skip.OverlayContent.IsPresent); + AddAssert("ensure button didn't disappear", () => skip.FadingContent.Alpha > 0); AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left)); checkRequestCount(0); } @@ -121,7 +121,9 @@ namespace osu.Game.Tests.Visual.Gameplay { } - public Drawable OverlayContents => (InternalChild as Container)?.Child; + public Drawable OverlayContent => InternalChild; + + public Drawable FadingContent => (OverlayContent as Container)?.Child; } } } From 013683c23ba1d02bf348ae6288d56b253adc9385 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 May 2020 00:17:13 +0900 Subject: [PATCH 17/39] Fix taiko rim markers incorrectly playing as whistle samples --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index d332f90cd4..1e1f9ae09b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -52,7 +52,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override IEnumerable GetSamples() { // normal and claps are always handled by the drum (see DrumSampleMapping). - var samples = HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP); + // in addition, whistles are excluded as they are an alternative rim marker. + + var samples = HitObject.Samples.Where(s => + s.Name != HitSampleInfo.HIT_NORMAL + && s.Name != HitSampleInfo.HIT_CLAP + && s.Name != HitSampleInfo.HIT_WHISTLE); if (HitObject.Type == HitType.Rim && HitObject.IsStrong) { From 9415e45aea5afb899edc21bd866accb0923c82eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 May 2020 20:11:19 +0200 Subject: [PATCH 18/39] Add overlay layer to enumeration type --- osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs index 48e8bdbb76..ea23c49c4a 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs @@ -9,6 +9,7 @@ namespace osu.Game.Beatmaps.Legacy Fail = 1, Pass = 2, Foreground = 3, - Video = 4 + Overlay = 4, + Video = 5 } } From e9710b6f836578850646db5e78e06f8985066a35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 May 2020 09:43:05 +0900 Subject: [PATCH 19/39] Add taiko type conversion test coverage --- .../TaikoBeatmapConversionTest.cs | 3 +- ...-type-conversions-expected-conversion.json | 116 ++++++++++++++++++ .../Beatmaps/sample-to-type-conversions.osu | 62 ++++++++++ 3 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions-expected-conversion.json create mode 100644 osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions.osu diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs index 8c26ca70ac..f7729138ff 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [NonParallelizable] [TestCase("basic")] [TestCase("slider-generating-drumroll")] + [TestCase("sample-to-type-conversions")] public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) @@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests public struct ConvertValue : IEquatable { /// - /// A sane value to account for osu!stable using ints everwhere. + /// A sane value to account for osu!stable using ints everywhere. /// private const float conversion_lenience = 2; diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions-expected-conversion.json b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions-expected-conversion.json new file mode 100644 index 0000000000..47ca6aef68 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions-expected-conversion.json @@ -0,0 +1,116 @@ +{ + "Mappings": [ + { + "StartTime": 110.0, + "Objects": [ + { + "StartTime": 110.0, + "EndTime": 110.0, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + } + ] + }, + { + "StartTime": 538.0, + "Objects": [ + { + "StartTime": 538.0, + "EndTime": 538.0, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + } + ] + }, + { + "StartTime": 967.0, + "Objects": [ + { + "StartTime": 967.0, + "EndTime": 967.0, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + } + ] + }, + { + "StartTime": 1395.0, + "Objects": [ + { + "StartTime": 1395.0, + "EndTime": 1395.0, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + } + ] + }, + { + "StartTime": 1824.0, + "Objects": [ + { + "StartTime": 1824.0, + "EndTime": 1824.0, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": true + } + ] + }, + { + "StartTime": 2252.0, + "Objects": [ + { + "StartTime": 2252.0, + "EndTime": 2252.0, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": true + } + ] + }, + { + "StartTime": 2681.0, + "Objects": [ + { + "StartTime": 2681.0, + "EndTime": 2681.0, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": true + } + ] + }, + { + "StartTime": 3110.0, + "Objects": [ + { + "StartTime": 3110.0, + "EndTime": 3110.0, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": true + } + ] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions.osu new file mode 100644 index 0000000000..a3537e7149 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions.osu @@ -0,0 +1,62 @@ +osu file format v14 + +[General] +AudioFilename: audio.mp3 +AudioLeadIn: 0 +PreviewTime: -1 +Countdown: 0 +SampleSet: Normal +StackLeniency: 0.5 +Mode: 1 +LetterboxInBreaks: 0 +WidescreenStoryboard: 1 + +[Editor] +Bookmarks: 110,13824,54967,82395,109824 +DistanceSpacing: 0.1 +BeatDivisor: 4 +GridSize: 32 +TimelineZoom: 3.099999 + +[Metadata] +Title:test +TitleUnicode:test +Artist:sample conversion +ArtistUnicode:sample conversion +Creator:banchobot +Version:sample test +Source: +Tags: +BeatmapID:0 +BeatmapSetID:-1 + +[Difficulty] +HPDrainRate:6 +CircleSize:2 +OverallDifficulty:6 +ApproachRate:7 +SliderMultiplier:1.4 +SliderTickRate:4 + +[Events] +//Background and Video events +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +110,428.571428571429,4,1,0,100,1,0 + +[HitObjects] +256,192,110,5,0,0:0:0:0: +256,192,538,1,8,0:0:0:0: +256,192,967,1,2,0:0:0:0: +256,192,1395,1,10,0:0:0:0: +256,192,1824,1,4,0:0:0:0: +256,192,2252,1,12,0:0:0:0: +256,192,2681,1,6,0:0:0:0: +256,192,3110,1,14,0:0:0:0: From 3ee698cfa02bf3cb7d24f37761a75b211810b702 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 May 2020 12:39:09 +0900 Subject: [PATCH 20/39] Fix being able to press enter to create matches --- .../Screens/Multi/Match/Components/MatchSettingsOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs index 5d68de9ce6..54c4f8f7c7 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -133,7 +133,6 @@ namespace osu.Game.Screens.Multi.Match.Components { RelativeSizeAxes = Axes.X, TabbableContentContainer = this, - OnCommit = (sender, text) => apply(), }, }, new Section("Duration") @@ -196,7 +195,6 @@ namespace osu.Game.Screens.Multi.Match.Components RelativeSizeAxes = Axes.X, TabbableContentContainer = this, ReadOnly = true, - OnCommit = (sender, text) => apply() }, }, new Section("Password (optional)") @@ -207,7 +205,6 @@ namespace osu.Game.Screens.Multi.Match.Components RelativeSizeAxes = Axes.X, TabbableContentContainer = this, ReadOnly = true, - OnCommit = (sender, text) => apply() }, }, }, @@ -331,6 +328,9 @@ namespace osu.Game.Screens.Multi.Match.Components private void apply() { + if (!ApplyButton.Enabled.Value) + return; + hideError(); RoomName.Value = NameField.Text; From 6d3ca4ec43c2963f9fee14be4508fafe10287c73 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 May 2020 13:16:46 +0900 Subject: [PATCH 21/39] Fix failing tests --- .../Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs index d2e8c22c39..34c6940552 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs @@ -69,6 +69,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { settings.NameField.Current.Value = expected_name; settings.DurationField.Current.Value = expectedDuration; + Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); roomManager.CreateRequested = r => { @@ -89,6 +90,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("setup", () => { + Room.Name.Value = "Test Room"; + Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + fail = true; roomManager.CreateRequested = _ => !fail; }); From 052ad79fc6e012b3901214bf4005a263a2fa16e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 May 2020 16:44:22 +0900 Subject: [PATCH 22/39] Convert dangerous events to IBindables --- .../Beatmaps/IO/ImportBeatmapTest.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 13 ++-- osu.Game/Database/ArchiveModelManager.cs | 13 ++-- .../DownloadableArchiveModelManager.cs | 15 +++-- osu.Game/Database/IModelDownloader.cs | 7 +- osu.Game/Database/IModelManager.cs | 7 +- osu.Game/Online/DownloadTrackingComposite.cs | 65 ++++++++++++------- osu.Game/OsuGameBase.cs | 13 +++- osu.Game/Overlays/MusicController.cs | 46 +++++++------ .../Overlays/Settings/Sections/SkinSection.cs | 30 +++++---- .../Multi/Match/Components/ReadyButton.cs | 50 +++++++------- .../Screens/Multi/Match/MatchSubScreen.cs | 26 ++++---- osu.Game/Screens/Select/BeatmapCarousel.cs | 49 +++++++++----- .../Screens/Select/Carousel/TopLocalRank.cs | 32 ++++----- .../Select/Leaderboards/BeatmapLeaderboard.cs | 15 ++--- osu.Game/Skinning/SkinManager.cs | 13 ++-- 16 files changed, 234 insertions(+), 164 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index ba6f5fc85c..43fab186aa 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -156,8 +156,8 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); // ReSharper disable once AccessToModifiedClosure - manager.ItemAdded += _ => Interlocked.Increment(ref itemAddRemoveFireCount); - manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount); + manager.ItemAdded.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount)); + manager.ItemRemoved.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount)); var imported = await LoadOszIntoOsu(osu); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 34ad1df6bc..7aaf0ca08d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics.Textures; using osu.Framework.Lists; @@ -38,12 +39,16 @@ namespace osu.Game.Beatmaps /// /// Fired when a single difficulty has been hidden. /// - public event Action BeatmapHidden; + public IBindable> BeatmapHidden => beatmapHidden; + + private readonly Bindable> beatmapHidden = new Bindable>(); /// /// Fired when a single difficulty has been restored. /// - public event Action BeatmapRestored; + public IBindable> BeatmapRestored => beatmapRestored; + + private readonly Bindable> beatmapRestored = new Bindable>(); /// /// A default representation of a WorkingBeatmap to use when no beatmap is available. @@ -74,8 +79,8 @@ namespace osu.Game.Beatmaps DefaultBeatmap = defaultBeatmap; beatmaps = (BeatmapStore)ModelStore; - beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); - beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); + beatmaps.BeatmapHidden += b => beatmapHidden.Value = new WeakReference(b); + beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference(b); onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); exportStorage = storage.GetStorageForDirectory("exports"); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 839f9075e5..33b16cbaaf 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -11,6 +11,7 @@ using Humanizer; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Logging; @@ -56,13 +57,17 @@ namespace osu.Game.Database /// Fired when a new becomes available in the database. /// This is not guaranteed to run on the update thread. /// - public event Action ItemAdded; + public IBindable> ItemAdded => itemAdded; + + private readonly Bindable> itemAdded = new Bindable>(); /// /// Fired when a is removed from the database. /// This is not guaranteed to run on the update thread. /// - public event Action ItemRemoved; + public IBindable> ItemRemoved => itemRemoved; + + private readonly Bindable> itemRemoved = new Bindable>(); public virtual string[] HandledExtensions => new[] { ".zip" }; @@ -82,8 +87,8 @@ namespace osu.Game.Database ContextFactory = contextFactory; ModelStore = modelStore; - ModelStore.ItemAdded += item => handleEvent(() => ItemAdded?.Invoke(item)); - ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s)); + ModelStore.ItemAdded += item => handleEvent(() => itemAdded.Value = new WeakReference(item)); + ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference(item)); Files = new FileStore(contextFactory, storage); diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs index 1b90898c8d..8f469ca590 100644 --- a/osu.Game/Database/DownloadableArchiveModelManager.cs +++ b/osu.Game/Database/DownloadableArchiveModelManager.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Bindables; namespace osu.Game.Database { @@ -23,9 +24,13 @@ namespace osu.Game.Database where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete, IEquatable where TFileModel : class, INamedFileInfo, new() { - public event Action> DownloadBegan; + public IBindable>> DownloadBegan => downloadBegan; - public event Action> DownloadFailed; + private readonly Bindable>> downloadBegan = new Bindable>>(); + + public IBindable>> DownloadFailed => downloadFailed; + + private readonly Bindable>> downloadFailed = new Bindable>>(); private readonly IAPIProvider api; @@ -81,7 +86,7 @@ namespace osu.Game.Database // for now a failed import will be marked as a failed download for simplicity. if (!imported.Any()) - DownloadFailed?.Invoke(request); + downloadFailed.Value = new WeakReference>(request); currentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); @@ -100,14 +105,14 @@ namespace osu.Game.Database api.PerformAsync(request); - DownloadBegan?.Invoke(request); + downloadBegan.Value = new WeakReference>(request); return true; void triggerFailure(Exception error) { currentDownloads.Remove(request); - DownloadFailed?.Invoke(request); + downloadFailed.Value = new WeakReference>(request); notification.State = ProgressNotificationState.Cancelled; diff --git a/osu.Game/Database/IModelDownloader.cs b/osu.Game/Database/IModelDownloader.cs index 99aeb4eacf..0cb633280e 100644 --- a/osu.Game/Database/IModelDownloader.cs +++ b/osu.Game/Database/IModelDownloader.cs @@ -1,8 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Online.API; using System; +using osu.Game.Online.API; +using osu.Framework.Bindables; namespace osu.Game.Database { @@ -17,13 +18,13 @@ namespace osu.Game.Database /// Fired when a download begins. /// This is NOT run on the update thread and should be scheduled. /// - event Action> DownloadBegan; + IBindable>> DownloadBegan { get; } /// /// Fired when a download is interrupted, either due to user cancellation or failure. /// This is NOT run on the update thread and should be scheduled. /// - event Action> DownloadFailed; + IBindable>> DownloadFailed { get; } /// /// Checks whether a given is already available in the local store. diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs index 1bdbbb48e6..852b385798 100644 --- a/osu.Game/Database/IModelManager.cs +++ b/osu.Game/Database/IModelManager.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; namespace osu.Game.Database { @@ -9,11 +10,11 @@ namespace osu.Game.Database /// Represents a model manager that publishes events when s are added or removed. /// /// The model type. - public interface IModelManager + public interface IModelManager where TModel : class { - event Action ItemAdded; + IBindable> ItemAdded { get; } - event Action ItemRemoved; + IBindable> ItemRemoved { get; } } } diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 0769be2998..47de7d75ed 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -34,6 +34,11 @@ namespace osu.Game.Online Model.Value = model; } + private IBindable> managerAdded; + private IBindable> managerRemoved; + private IBindable>> managerDownloadBegan; + private IBindable>> managerDownloadFailed; + [BackgroundDependencyLoader(true)] private void load() { @@ -47,23 +52,39 @@ namespace osu.Game.Online attachDownload(manager.GetExistingDownload(modelInfo.NewValue)); }, true); - manager.DownloadBegan += downloadBegan; - manager.DownloadFailed += downloadFailed; - manager.ItemAdded += itemAdded; - manager.ItemRemoved += itemRemoved; + managerDownloadBegan = manager.DownloadBegan.GetBoundCopy(); + managerDownloadBegan.BindValueChanged(downloadBegan); + managerDownloadFailed = manager.DownloadFailed.GetBoundCopy(); + managerDownloadFailed.BindValueChanged(downloadFailed); + managerAdded = manager.ItemAdded.GetBoundCopy(); + managerAdded.BindValueChanged(itemAdded); + managerRemoved = manager.ItemRemoved.GetBoundCopy(); + managerRemoved.BindValueChanged(itemRemoved); } - private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => + private void downloadBegan(ValueChangedEvent>> weakRequest) { - if (request.Model.Equals(Model.Value)) - attachDownload(request); - }); + if (weakRequest.NewValue.TryGetTarget(out var request)) + { + Schedule(() => + { + if (request.Model.Equals(Model.Value)) + attachDownload(request); + }); + } + } - private void downloadFailed(ArchiveDownloadRequest request) => Schedule(() => + private void downloadFailed(ValueChangedEvent>> weakRequest) { - if (request.Model.Equals(Model.Value)) - attachDownload(null); - }); + if (weakRequest.NewValue.TryGetTarget(out var request)) + { + Schedule(() => + { + if (request.Model.Equals(Model.Value)) + attachDownload(null); + }); + } + } private ArchiveDownloadRequest attachedRequest; @@ -107,9 +128,17 @@ namespace osu.Game.Online private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); - private void itemAdded(TModel s) => setDownloadStateFromManager(s, DownloadState.LocallyAvailable); + private void itemAdded(ValueChangedEvent> weakItem) + { + if (weakItem.NewValue.TryGetTarget(out var item)) + setDownloadStateFromManager(item, DownloadState.LocallyAvailable); + } - private void itemRemoved(TModel s) => setDownloadStateFromManager(s, DownloadState.NotDownloaded); + private void itemRemoved(ValueChangedEvent> weakItem) + { + if (weakItem.NewValue.TryGetTarget(out var item)) + setDownloadStateFromManager(item, DownloadState.NotDownloaded); + } private void setDownloadStateFromManager(TModel s, DownloadState state) => Schedule(() => { @@ -125,14 +154,6 @@ namespace osu.Game.Online { base.Dispose(isDisposing); - if (manager != null) - { - manager.DownloadBegan -= downloadBegan; - manager.DownloadFailed -= downloadFailed; - manager.ItemAdded -= itemAdded; - manager.ItemRemoved -= itemRemoved; - } - State.UnbindAll(); attachDownload(null); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 11a3834c71..c367c3b636 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -186,8 +186,17 @@ namespace osu.Game return ScoreManager.QueryScores(s => beatmapIds.Contains(s.Beatmap.ID)).ToList(); } - BeatmapManager.ItemRemoved += i => ScoreManager.Delete(getBeatmapScores(i), true); - BeatmapManager.ItemAdded += i => ScoreManager.Undelete(getBeatmapScores(i), true); + BeatmapManager.ItemRemoved.BindValueChanged(i => + { + if (i.NewValue.TryGetTarget(out var item)) + ScoreManager.Delete(getBeatmapScores(item), true); + }); + + BeatmapManager.ItemAdded.BindValueChanged(i => + { + if (i.NewValue.TryGetTarget(out var item)) + ScoreManager.Undelete(getBeatmapScores(item), true); + }); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index ded641b262..35f3cb0e25 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -60,11 +60,16 @@ namespace osu.Game.Overlays [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } + private IBindable> managerAdded; + private IBindable> managerRemoved; + [BackgroundDependencyLoader] private void load() { - beatmaps.ItemAdded += handleBeatmapAdded; - beatmaps.ItemRemoved += handleBeatmapRemoved; + managerAdded = beatmaps.ItemAdded.GetBoundCopy(); + managerAdded.BindValueChanged(beatmapAdded); + managerRemoved = beatmaps.ItemRemoved.GetBoundCopy(); + managerRemoved.BindValueChanged(beatmapRemoved); beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal).OrderBy(_ => RNG.Next())); } @@ -93,16 +98,28 @@ namespace osu.Game.Overlays /// public bool IsPlaying => current?.Track.IsRunning ?? false; - private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() => + private void beatmapAdded(ValueChangedEvent> weakSet) { - if (!beatmapSets.Contains(set)) - beatmapSets.Add(set); - }); + if (weakSet.NewValue.TryGetTarget(out var set)) + { + Schedule(() => + { + if (!beatmapSets.Contains(set)) + beatmapSets.Add(set); + }); + } + } - private void handleBeatmapRemoved(BeatmapSetInfo set) => Schedule(() => + private void beatmapRemoved(ValueChangedEvent> weakSet) { - beatmapSets.RemoveAll(s => s.ID == set.ID); - }); + if (weakSet.NewValue.TryGetTarget(out var set)) + { + Schedule(() => + { + beatmapSets.RemoveAll(s => s.ID == set.ID); + }); + } + } private ScheduledDelegate seekDelegate; @@ -299,17 +316,6 @@ namespace osu.Game.Overlays } } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (beatmaps != null) - { - beatmaps.ItemAdded -= handleBeatmapAdded; - beatmaps.ItemRemoved -= handleBeatmapRemoved; - } - } - public bool OnPressed(GlobalAction action) { if (beatmap.Disabled) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 75c8db1612..94080f5592 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.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.Bindables; @@ -30,6 +31,9 @@ namespace osu.Game.Overlays.Settings.Sections [Resolved] private SkinManager skins { get; set; } + private IBindable> managerAdded; + private IBindable> managerRemoved; + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -66,8 +70,11 @@ namespace osu.Game.Overlays.Settings.Sections }, }; - skins.ItemAdded += itemAdded; - skins.ItemRemoved += itemRemoved; + managerAdded = skins.ItemAdded.GetBoundCopy(); + managerAdded.BindValueChanged(itemAdded); + + managerRemoved = skins.ItemRemoved.GetBoundCopy(); + managerRemoved.BindValueChanged(itemRemoved); config.BindWith(OsuSetting.Skin, configBindable); @@ -82,19 +89,16 @@ namespace osu.Game.Overlays.Settings.Sections dropdownBindable.BindValueChanged(skin => configBindable.Value = skin.NewValue.ID); } - private void itemRemoved(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != s.ID).ToArray()); - - private void itemAdded(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Append(s).ToArray()); - - protected override void Dispose(bool isDisposing) + private void itemAdded(ValueChangedEvent> weakItem) { - base.Dispose(isDisposing); + if (weakItem.NewValue.TryGetTarget(out var item)) + Schedule(() => skinDropdown.Items = skinDropdown.Items.Append(item).ToArray()); + } - if (skins != null) - { - skins.ItemAdded -= itemAdded; - skins.ItemRemoved -= itemRemoved; - } + private void itemRemoved(ValueChangedEvent> weakItem) + { + if (weakItem.NewValue.TryGetTarget(out var item)) + Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != item.ID).ToArray()); } private class SizeSlider : OsuSliderBar diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs index 8f484d3672..4420b2d58a 100644 --- a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs +++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs @@ -32,11 +32,16 @@ namespace osu.Game.Screens.Multi.Match.Components Text = "Start"; } + private IBindable> managerAdded; + private IBindable> managerRemoved; + [BackgroundDependencyLoader] private void load(OsuColour colours) { - beatmaps.ItemAdded += beatmapAdded; - beatmaps.ItemRemoved += beatmapRemoved; + managerAdded = beatmaps.ItemAdded.GetBoundCopy(); + managerAdded.BindValueChanged(beatmapAdded); + managerRemoved = beatmaps.ItemRemoved.GetBoundCopy(); + managerRemoved.BindValueChanged(beatmapRemoved); SelectedItem.BindValueChanged(item => updateSelectedItem(item.NewValue), true); @@ -56,24 +61,30 @@ namespace osu.Game.Screens.Multi.Match.Components hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId) != null; } - private void beatmapAdded(BeatmapSetInfo model) + private void beatmapAdded(ValueChangedEvent> weakSet) { - int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; - if (beatmapId == null) - return; + if (weakSet.NewValue.TryGetTarget(out var set)) + { + int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; + if (beatmapId == null) + return; - if (model.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId)) - Schedule(() => hasBeatmap = true); + if (set.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId)) + Schedule(() => hasBeatmap = true); + } } - private void beatmapRemoved(BeatmapSetInfo model) + private void beatmapRemoved(ValueChangedEvent> weakSet) { - int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; - if (beatmapId == null) - return; + if (weakSet.NewValue.TryGetTarget(out var set)) + { + int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; + if (beatmapId == null) + return; - if (model.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId)) - Schedule(() => hasBeatmap = false); + if (set.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId)) + Schedule(() => hasBeatmap = false); + } } protected override void Update() @@ -95,16 +106,5 @@ namespace osu.Game.Screens.Multi.Match.Components Enabled.Value = hasBeatmap && hasEnoughTime; } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (beatmaps != null) - { - beatmaps.ItemAdded -= beatmapAdded; - beatmaps.ItemRemoved -= beatmapRemoved; - } - } } } diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index eef53126c0..caa547ac72 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -50,6 +50,8 @@ namespace osu.Game.Screens.Multi.Match private LeaderboardChatDisplay leaderboardChatDisplay; private MatchSettingsOverlay settingsOverlay; + private IBindable> managerAdded; + public MatchSubScreen(Room room) { Title = room.RoomID.Value == null ? "New room" : room.Name.Value; @@ -181,7 +183,8 @@ namespace osu.Game.Screens.Multi.Match SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); SelectedItem.Value = playlist.FirstOrDefault(); - beatmapManager.ItemAdded += beatmapAdded; + managerAdded = beatmapManager.ItemAdded.GetBoundCopy(); + managerAdded.BindValueChanged(beatmapAdded); } public override bool OnExiting(IScreen next) @@ -214,13 +217,16 @@ namespace osu.Game.Screens.Multi.Match Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } - private void beatmapAdded(BeatmapSetInfo model) => Schedule(() => + private void beatmapAdded(ValueChangedEvent> weakSet) { - if (Beatmap.Value != beatmapManager.DefaultBeatmap) - return; + Schedule(() => + { + if (Beatmap.Value != beatmapManager.DefaultBeatmap) + return; - updateWorkingBeatmap(); - }); + updateWorkingBeatmap(); + }); + } private void onStart() { @@ -235,13 +241,5 @@ namespace osu.Game.Screens.Multi.Match break; } } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (beatmapManager != null) - beatmapManager.ItemAdded -= beatmapAdded; - } } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 96b779cd20..f23e1b1ef2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -131,6 +131,11 @@ namespace osu.Game.Screens.Select private CarouselRoot root; + private IBindable> itemAdded; + private IBindable> itemRemoved; + private IBindable> itemHidden; + private IBindable> itemRestored; + public BeatmapCarousel() { root = new CarouselRoot(this); @@ -161,10 +166,14 @@ namespace osu.Game.Screens.Select RightClickScrollingEnabled.ValueChanged += enabled => scroll.RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); - beatmaps.ItemAdded += beatmapAdded; - beatmaps.ItemRemoved += beatmapRemoved; - beatmaps.BeatmapHidden += beatmapHidden; - beatmaps.BeatmapRestored += beatmapRestored; + itemAdded = beatmaps.ItemAdded.GetBoundCopy(); + itemAdded.BindValueChanged(beatmapAdded); + itemRemoved = beatmaps.ItemRemoved.GetBoundCopy(); + itemRemoved.BindValueChanged(beatmapRemoved); + itemHidden = beatmaps.BeatmapHidden.GetBoundCopy(); + itemHidden.BindValueChanged(beatmapHidden); + itemRestored = beatmaps.BeatmapRestored.GetBoundCopy(); + itemRestored.BindValueChanged(beatmapRestored); loadBeatmapSets(GetLoadableBeatmaps()); } @@ -562,26 +571,34 @@ namespace osu.Game.Screens.Select { base.Dispose(isDisposing); - if (beatmaps != null) - { - beatmaps.ItemAdded -= beatmapAdded; - beatmaps.ItemRemoved -= beatmapRemoved; - beatmaps.BeatmapHidden -= beatmapHidden; - beatmaps.BeatmapRestored -= beatmapRestored; - } - // aggressively dispose "off-screen" items to reduce GC pressure. foreach (var i in Items) i.Dispose(); } - private void beatmapRemoved(BeatmapSetInfo item) => RemoveBeatmapSet(item); + private void beatmapRemoved(ValueChangedEvent> weakItem) + { + if (weakItem.NewValue.TryGetTarget(out var item)) + RemoveBeatmapSet(item); + } - private void beatmapAdded(BeatmapSetInfo item) => UpdateBeatmapSet(item); + private void beatmapAdded(ValueChangedEvent> weakItem) + { + if (weakItem.NewValue.TryGetTarget(out var item)) + UpdateBeatmapSet(item); + } - private void beatmapRestored(BeatmapInfo b) => UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); + private void beatmapRestored(ValueChangedEvent> weakItem) + { + if (weakItem.NewValue.TryGetTarget(out var b)) + UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); + } - private void beatmapHidden(BeatmapInfo b) => UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); + private void beatmapHidden(ValueChangedEvent> weakItem) + { + if (weakItem.NewValue.TryGetTarget(out var b)) + UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); + } private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) { diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index e981550c84..aed25787b0 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.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.Bindables; @@ -27,6 +28,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private IAPIProvider api { get; set; } + private IBindable> itemAdded; + private IBindable> itemRemoved; + public TopLocalRank(BeatmapInfo beatmap) : base(null) { @@ -36,17 +40,24 @@ namespace osu.Game.Screens.Select.Carousel [BackgroundDependencyLoader] private void load() { - scores.ItemAdded += scoreChanged; - scores.ItemRemoved += scoreChanged; + itemAdded = scores.ItemAdded.GetBoundCopy(); + itemAdded.BindValueChanged(scoreChanged); + + itemRemoved = scores.ItemRemoved.GetBoundCopy(); + itemRemoved.BindValueChanged(scoreChanged); + ruleset.ValueChanged += _ => fetchAndLoadTopScore(); fetchAndLoadTopScore(); } - private void scoreChanged(ScoreInfo score) + private void scoreChanged(ValueChangedEvent> weakScore) { - if (score.BeatmapInfoID == beatmap.ID) - fetchAndLoadTopScore(); + if (weakScore.NewValue.TryGetTarget(out var score)) + { + if (score.BeatmapInfoID == beatmap.ID) + fetchAndLoadTopScore(); + } } private ScheduledDelegate scheduledRankUpdate; @@ -75,16 +86,5 @@ namespace osu.Game.Screens.Select.Carousel .OrderByDescending(s => s.TotalScore) .FirstOrDefault(); } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (scores != null) - { - scores.ItemAdded -= scoreChanged; - scores.ItemRemoved -= scoreChanged; - } - } } } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index e36493c82f..8e85eb4eb2 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -60,6 +60,8 @@ namespace osu.Game.Screens.Select.Leaderboards private UserTopScoreContainer topScoreContainer; + private IBindable> itemRemoved; + /// /// Whether to apply the game's currently selected mods as a filter when retrieving scores. /// @@ -104,7 +106,8 @@ namespace osu.Game.Screens.Select.Leaderboards ScoreSelected = s => ScoreSelected?.Invoke(s) }); - scoreManager.ItemRemoved += onScoreRemoved; + itemRemoved = scoreManager.ItemRemoved.GetBoundCopy(); + itemRemoved.BindValueChanged(onScoreRemoved); } protected override void Reset() @@ -113,7 +116,7 @@ namespace osu.Game.Screens.Select.Leaderboards TopScore = null; } - private void onScoreRemoved(ScoreInfo score) => Schedule(RefreshScores); + private void onScoreRemoved(ValueChangedEvent> score) => Schedule(RefreshScores); protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; @@ -190,13 +193,5 @@ namespace osu.Game.Screens.Select.Leaderboards { Action = () => ScoreSelected?.Invoke(model) }; - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (scoreManager != null) - scoreManager.ItemRemoved -= onScoreRemoved; - } } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 3d469ab6e1..d65c74ef62 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -43,12 +43,15 @@ namespace osu.Game.Skinning this.audio = audio; this.legacyDefaultResources = legacyDefaultResources; - ItemRemoved += removedInfo => + ItemRemoved.BindValueChanged(weakRemovedInfo => { - // check the removed skin is not the current user choice. if it is, switch back to default. - if (removedInfo.ID == CurrentSkinInfo.Value.ID) - CurrentSkinInfo.Value = SkinInfo.Default; - }; + if (weakRemovedInfo.NewValue.TryGetTarget(out var removedInfo)) + { + // check the removed skin is not the current user choice. if it is, switch back to default. + if (removedInfo.ID == CurrentSkinInfo.Value.ID) + CurrentSkinInfo.Value = SkinInfo.Default; + } + }); CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue); CurrentSkin.ValueChanged += skin => From db4e3047ddf02cb09a7603c68a0ab854e1cfe6b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 May 2020 23:28:13 +0900 Subject: [PATCH 23/39] Add test for final sample output --- .../TestSceneSampleOutput.cs | 47 +++++++++++++++++++ .../Objects/Drawables/DrawableHit.cs | 2 +- .../Drawables/DrawableTaikoHitObject.cs | 2 +- .../Objects/Drawables/DrawableHitObject.cs | 2 +- .../Tests/Beatmaps/BeatmapConversionTest.cs | 11 +++-- 5 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs new file mode 100644 index 0000000000..564ab91291 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + /// + /// Taiko has some interesting rules for legacy mappings. + /// + public class TestSceneSampleOutput : PlayerTestScene + { + public TestSceneSampleOutput() + : base(new TaikoRuleset()) + { + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + AddAssert("has correct samples", () => + { + var names = Player.DrawableRuleset.Playfield.AllHitObjects.OfType().Select(h => string.Join(',', h.GetSamples().Select(s => s.Name))); + + var expected = new[] + { + string.Empty, + string.Empty, + string.Empty, + string.Empty, + HitSampleInfo.HIT_FINISH, + HitSampleInfo.HIT_WHISTLE, + HitSampleInfo.HIT_WHISTLE, + HitSampleInfo.HIT_WHISTLE, + }; + + return names.SequenceEqual(expected); + }); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TaikoBeatmapConversionTest().GetBeatmap("sample-to-type-conversions"); + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 1e1f9ae09b..81b969eaf3 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ? new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit), _ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit) : new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit), _ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit); - protected override IEnumerable GetSamples() + public override IEnumerable GetSamples() { // normal and claps are always handled by the drum (see DrumSampleMapping). // in addition, whistles are excluded as they are an alternative rim marker. diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 90daf3950c..3ab09d4cbe 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } // Most osu!taiko hitsounds are managed by the drum (see DrumSampleMapping). - protected override IEnumerable GetSamples() => Enumerable.Empty(); + public override IEnumerable GetSamples() => Enumerable.Empty(); protected abstract SkinnableDrawable CreateMainPiece(); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ba6571fe1a..33ea02c22f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Objects.Drawables protected SkinnableSound Samples { get; private set; } - protected virtual IEnumerable GetSamples() => HitObject.Samples; + public virtual IEnumerable GetSamples() => HitObject.Samples; private readonly Lazy> nestedHitObjects = new Lazy>(); public IReadOnlyList NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : (IReadOnlyList)Array.Empty(); diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index b60add6e3b..06e82394ec 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Beatmaps private ConvertResult convert(string name, Mod[] mods) { - var beatmap = getBeatmap(name); + var beatmap = GetBeatmap(name); var rulesetInstance = CreateRuleset(); beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo(); @@ -143,14 +143,19 @@ namespace osu.Game.Tests.Beatmaps } } - private IBeatmap getBeatmap(string name) + public IBeatmap GetBeatmap(string name) { using (var resStream = openResource($"{resource_namespace}.{name}.osu")) using (var stream = new LineBufferedReader(resStream)) { var decoder = Decoder.GetDecoder(stream); ((LegacyBeatmapDecoder)decoder).ApplyOffsets = false; - return decoder.Decode(stream); + var beatmap = decoder.Decode(stream); + + // not sure but seems to be required. + beatmap.BeatmapInfo.Ruleset = CreateRuleset().RulesetInfo; + + return beatmap; } } From e21178570484780c8467f47529ea407a6fd5e24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 May 2020 21:01:13 +0200 Subject: [PATCH 24/39] Add overlay layer to storyboard definition --- osu.Game/Storyboards/Storyboard.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index d13c874ee2..b0fb583d62 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -19,19 +19,26 @@ namespace osu.Game.Storyboards public double FirstEventTime => Layers.Min(l => l.Elements.FirstOrDefault()?.StartTime ?? 0); + /// + /// Depth of the currently front-most storyboard layer, excluding the overlay layer. + /// + private int minimumLayerDepth; + public Storyboard() { layers.Add("Video", new StoryboardLayer("Video", 4, false)); layers.Add("Background", new StoryboardLayer("Background", 3)); layers.Add("Fail", new StoryboardLayer("Fail", 2) { VisibleWhenPassing = false, }); layers.Add("Pass", new StoryboardLayer("Pass", 1) { VisibleWhenFailing = false, }); - layers.Add("Foreground", new StoryboardLayer("Foreground", 0)); + layers.Add("Foreground", new StoryboardLayer("Foreground", minimumLayerDepth = 0)); + + layers.Add("Overlay", new StoryboardLayer("Overlay", int.MinValue)); } public StoryboardLayer GetLayer(string name) { if (!layers.TryGetValue(name, out var layer)) - layers[name] = layer = new StoryboardLayer(name, layers.Values.Min(l => l.Depth) - 1); + layers[name] = layer = new StoryboardLayer(name, --minimumLayerDepth); return layer; } From 6e27247cdf7bc46b4317cbb98f3dfb5b20c769a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 May 2020 22:10:02 +0200 Subject: [PATCH 25/39] Adjust storyboard decoder test in line with changes --- .../Beatmaps/Formats/LegacyStoryboardDecoderTest.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 2fdeadca02..9ebedb3c80 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var storyboard = decoder.Decode(stream); Assert.IsTrue(storyboard.HasDrawable); - Assert.AreEqual(5, storyboard.Layers.Count()); + Assert.AreEqual(6, storyboard.Layers.Count()); StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3); Assert.IsNotNull(background); @@ -56,6 +56,13 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(foreground.VisibleWhenPassing); Assert.AreEqual("Foreground", foreground.Name); + StoryboardLayer overlay = storyboard.Layers.FirstOrDefault(l => l.Depth == int.MinValue); + Assert.IsNotNull(overlay); + Assert.IsEmpty(overlay.Elements); + Assert.IsTrue(overlay.VisibleWhenFailing); + Assert.IsTrue(overlay.VisibleWhenPassing); + Assert.AreEqual("Overlay", overlay.Name); + int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite)); int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation)); int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSampleInfo)); From 2398f2e537e8375b63f0de39b637751c1cbaca96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 May 2020 21:12:14 +0200 Subject: [PATCH 26/39] Expose drawable overlay layer --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 3 +++ osu.Game/Storyboards/StoryboardLayer.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index c4d796e30b..ec461fa095 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using System.Threading; using osuTK; using osu.Framework.Allocation; @@ -72,6 +73,8 @@ namespace osu.Game.Storyboards.Drawables } } + public DrawableStoryboardLayer OverlayLayer => Children.Single(layer => layer.Name == "Overlay"); + private void updateLayerVisibility() { foreach (var layer in Children) diff --git a/osu.Game/Storyboards/StoryboardLayer.cs b/osu.Game/Storyboards/StoryboardLayer.cs index 142bc60deb..1cde7cf67a 100644 --- a/osu.Game/Storyboards/StoryboardLayer.cs +++ b/osu.Game/Storyboards/StoryboardLayer.cs @@ -33,6 +33,6 @@ namespace osu.Game.Storyboards } public DrawableStoryboardLayer CreateDrawable() - => new DrawableStoryboardLayer(this) { Depth = Depth, }; + => new DrawableStoryboardLayer(this) { Depth = Depth, Name = Name }; } } From ce4301c5b8a5a36dafb4dac54d04c4eb49920b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 May 2020 19:47:01 +0200 Subject: [PATCH 27/39] Add overlay layer to player by proxying --- osu.Game/Screens/Play/DimmableStoryboard.cs | 15 +++++++++++++-- osu.Game/Screens/Play/Player.cs | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index eabdee95fb..74c84f648c 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; @@ -13,6 +14,8 @@ namespace osu.Game.Screens.Play /// public class DimmableStoryboard : UserDimContainer { + public Container OverlayLayerContainer; + private readonly Storyboard storyboard; private DrawableStoryboard drawableStoryboard; @@ -24,6 +27,8 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { + Add(OverlayLayerContainer = new Container()); + initializeStoryboard(false); } @@ -46,9 +51,15 @@ namespace osu.Game.Screens.Play drawableStoryboard = storyboard.CreateDrawable(); if (async) - LoadComponentAsync(drawableStoryboard, Add); + LoadComponentAsync(drawableStoryboard, onStoryboardCreated); else - Add(drawableStoryboard); + onStoryboardCreated(drawableStoryboard); + } + + private void onStoryboardCreated(DrawableStoryboard storyboard) + { + Add(storyboard); + OverlayLayerContainer.Add(storyboard.OverlayLayer.CreateProxy()); } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 1ec3a69b24..77da038ab3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -264,6 +264,7 @@ namespace osu.Game.Screens.Play { target.AddRange(new[] { + DimmableStoryboard.OverlayLayerContainer.CreateProxy(), BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { Clock = DrawableRuleset.FrameStableClock, From 963806474148d80ddc706e0f47f8bf5d8a22e6a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 10:06:23 +0900 Subject: [PATCH 28/39] Tidy up ruleset assignment code --- osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 06e82394ec..6ada632850 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -101,9 +101,6 @@ namespace osu.Game.Tests.Beatmaps { var beatmap = GetBeatmap(name); - var rulesetInstance = CreateRuleset(); - beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo(); - var converterResult = new Dictionary>(); var working = new ConversionWorkingBeatmap(beatmap) @@ -115,7 +112,7 @@ namespace osu.Game.Tests.Beatmaps } }; - working.GetPlayableBeatmap(rulesetInstance.RulesetInfo, mods); + working.GetPlayableBeatmap(CreateRuleset().RulesetInfo, mods); return new ConvertResult { @@ -152,8 +149,8 @@ namespace osu.Game.Tests.Beatmaps ((LegacyBeatmapDecoder)decoder).ApplyOffsets = false; var beatmap = decoder.Decode(stream); - // not sure but seems to be required. - beatmap.BeatmapInfo.Ruleset = CreateRuleset().RulesetInfo; + var rulesetInstance = CreateRuleset(); + beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo(); return beatmap; } From 76080368e900f2b866f46235eebff0f7bda19a6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 10:14:08 +0900 Subject: [PATCH 29/39] Mark test as headless --- osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs index 564ab91291..d541aa8de8 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Taiko.Objects.Drawables; @@ -12,6 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Tests /// /// Taiko has some interesting rules for legacy mappings. /// + [HeadlessTest] public class TestSceneSampleOutput : PlayerTestScene { public TestSceneSampleOutput() From 85088c9b3baa96ece3ad5a1404585eeb23e5c8bb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 May 2020 15:08:33 +0900 Subject: [PATCH 30/39] Privatise setter --- osu.Game/Screens/Play/DimmableStoryboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 74c84f648c..58eb95b7c6 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play /// public class DimmableStoryboard : UserDimContainer { - public Container OverlayLayerContainer; + public Container OverlayLayerContainer { get; private set; } private readonly Storyboard storyboard; private DrawableStoryboard drawableStoryboard; From c2697d39070d73ffbb2b7894a57088a85df2e127 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 20:49:01 +0900 Subject: [PATCH 31/39] Use DrawableSample in SkinnableSound class --- .../Objects/Drawables/DrawableHitObject.cs | 11 ++- osu.Game/Skinning/SkinnableSound.cs | 72 +++++++------------ 2 files changed, 32 insertions(+), 51 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 33ea02c22f..c32d4e441e 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,7 +11,6 @@ using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; -using osu.Framework.Audio; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; @@ -96,8 +95,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected virtual float SamplePlaybackPosition => 0.5f; - private readonly BindableDouble balanceAdjust = new BindableDouble(); - private BindableList samplesBindable; private Bindable startTimeBindable; private Bindable userPositionalHitSounds; @@ -173,7 +170,6 @@ namespace osu.Game.Rulesets.Objects.Drawables } Samples = new SkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s))); - Samples.AddAdjustment(AdjustableProperty.Balance, balanceAdjust); AddInternal(Samples); } @@ -360,8 +356,11 @@ namespace osu.Game.Rulesets.Objects.Drawables { const float balance_adjust_amount = 0.4f; - balanceAdjust.Value = balance_adjust_amount * (userPositionalHitSounds.Value ? SamplePlaybackPosition - 0.5f : 0); - Samples?.Play(); + if (Samples != null) + { + Samples.Balance.Value = balance_adjust_amount * (userPositionalHitSounds.Value ? SamplePlaybackPosition - 0.5f : 0); + Samples.Play(); + } } protected override void Update() diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index a78c04ecd4..30320c89a6 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -4,11 +4,11 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Audio; +using osu.Framework.Graphics.Containers; using osu.Game.Audio; namespace osu.Game.Skinning @@ -17,25 +17,32 @@ namespace osu.Game.Skinning { private readonly ISampleInfo[] hitSamples; - private List<(AdjustableProperty property, BindableDouble bindable)> adjustments; - - private SampleChannel[] channels; - [Resolved] private ISampleStore samples { get; set; } + public SkinnableSound(ISampleInfo hitSamples) + : this(new[] { hitSamples }) + { + } + public SkinnableSound(IEnumerable hitSamples) { this.hitSamples = hitSamples.ToArray(); - } - - public SkinnableSound(ISampleInfo hitSamples) - { - this.hitSamples = new[] { hitSamples }; + InternalChild = samplesContainer = new AudioContainer(); } private bool looping; + private readonly AudioContainer samplesContainer; + + public BindableNumber Volume => samplesContainer.Volume; + + public BindableNumber Balance => samplesContainer.Balance; + + public BindableNumber Frequency => samplesContainer.Frequency; + + public BindableNumber Tempo => samplesContainer.Tempo; + public bool Looping { get => looping; @@ -45,33 +52,23 @@ namespace osu.Game.Skinning looping = value; - channels?.ForEach(c => c.Looping = looping); + samplesContainer.ForEach(c => c.Looping = looping); } } - public void Play() => channels?.ForEach(c => c.Play()); - - public void Stop() => channels?.ForEach(c => c.Stop()); - - public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) + public void Play() => samplesContainer.ForEach(c => { - if (adjustments == null) adjustments = new List<(AdjustableProperty, BindableDouble)>(); + if (c.AggregateVolume.Value > 0) + c.Play(); + }); - adjustments.Add((type, adjustBindable)); - channels?.ForEach(c => c.AddAdjustment(type, adjustBindable)); - } - - public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) - { - adjustments?.Remove((type, adjustBindable)); - channels?.ForEach(c => c.RemoveAdjustment(type, adjustBindable)); - } + public void Stop() => samplesContainer.ForEach(c => c.Stop()); public override bool IsPresent => Scheduler.HasPendingTasks; protected override void SkinChanged(ISkinSource skin, bool allowFallback) { - channels = hitSamples.Select(s => + var channels = hitSamples.Select(s => { var ch = skin.GetSample(s); @@ -88,27 +85,12 @@ namespace osu.Game.Skinning { ch.Looping = looping; ch.Volume.Value = s.Volume / 100.0; - - if (adjustments != null) - { - foreach (var (property, bindable) in adjustments) - ch.AddAdjustment(property, bindable); - } } return ch; - }).Where(c => c != null).ToArray(); - } + }).Where(c => c != null); - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (channels != null) - { - foreach (var c in channels) - c.Dispose(); - } + samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c)); } } } From b5a7023312d72670a155b6435354cccd817aa748 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 21:46:52 +0900 Subject: [PATCH 32/39] Seek to start time after placement, not end --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 883288d6d7..10dffc6aa8 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -257,7 +257,7 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.Add(hitObject); - adjustableClock.Seek(hitObject.GetEndTime()); + adjustableClock.Seek(hitObject.StartTime); } showGridFor(Enumerable.Empty()); From e09a1bf546d7bdd12cdd5670c2d0b2398c8242a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 21:50:52 +0900 Subject: [PATCH 33/39] Only seek forwards if not already beyond the placed object --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 10dffc6aa8..67216b019d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -257,7 +257,8 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.Add(hitObject); - adjustableClock.Seek(hitObject.StartTime); + if (adjustableClock.CurrentTime < hitObject.StartTime) + adjustableClock.Seek(hitObject.StartTime); } showGridFor(Enumerable.Empty()); From ce223a2bd84c6362fd1f78aa6bef4feadc8bacb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 May 2020 10:58:30 +0900 Subject: [PATCH 34/39] Silence hit sounds while seeking --- .../Objects/Drawables/DrawableHitObject.cs | 6 ++++- .../Rulesets/UI/FrameStabilityContainer.cs | 22 +++++++++++++++---- osu.Game/Screens/Play/GameplayClock.cs | 5 +++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index c32d4e441e..d594909cda 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Configuration; +using osu.Game.Screens.Play; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -348,6 +349,9 @@ namespace osu.Game.Rulesets.Objects.Drawables { } + [Resolved(canBeNull: true)] + private GameplayClock gameplayClock { get; set; } + /// /// Plays all the hit sounds for this . /// This is invoked automatically when this is hit. @@ -356,7 +360,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { const float balance_adjust_amount = 0.4f; - if (Samples != null) + if (Samples != null && gameplayClock?.IsSeeking != true) { Samples.Balance.Value = balance_adjust_amount * (userPositionalHitSounds.Value ? SamplePlaybackPosition - 0.5f : 0); Samples.Play(); diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 3ba28aad45..bc9401a095 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -29,14 +29,16 @@ namespace osu.Game.Rulesets.UI /// internal bool FrameStablePlayback = true; - [Cached] - public GameplayClock GameplayClock { get; } + public GameplayClock GameplayClock => stabilityGameplayClock; + + [Cached(typeof(GameplayClock))] + private readonly StabilityGameplayClock stabilityGameplayClock; public FrameStabilityContainer(double gameplayStartTime = double.MinValue) { RelativeSizeAxes = Axes.Both; - GameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + stabilityGameplayClock = new StabilityGameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); this.gameplayStartTime = gameplayStartTime; } @@ -57,7 +59,7 @@ namespace osu.Game.Rulesets.UI { if (clock != null) { - parentGameplayClock = clock; + stabilityGameplayClock.ParentGameplayClock = parentGameplayClock = clock; GameplayClock.IsPaused.BindTo(clock.IsPaused); } } @@ -187,5 +189,17 @@ namespace osu.Game.Rulesets.UI } public ReplayInputHandler ReplayInputHandler { get; set; } + + private class StabilityGameplayClock : GameplayClock + { + public IFrameBasedClock ParentGameplayClock; + + public StabilityGameplayClock(FramedClock underlyingClock) + : base(underlyingClock) + { + } + + public override bool IsSeeking => ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200; + } } } diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index d5f75f6ad1..4f2cf5005c 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -31,6 +31,11 @@ namespace osu.Game.Screens.Play public bool IsRunning => underlyingClock.IsRunning; + /// + /// Whether an ongoing seek operation is active. + /// + public virtual bool IsSeeking => false; + public void ProcessFrame() { // we do not want to process the underlying clock. From c0e68f98540303aa02932718d1d96f2bf8a94c20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 May 2020 10:59:30 +0900 Subject: [PATCH 35/39] Also support taiko drum --- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 38026517d9..06ccd45cb8 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -12,6 +12,7 @@ using osu.Framework.Input.Bindings; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Audio; +using osu.Game.Screens.Play; using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.UI @@ -145,6 +146,9 @@ namespace osu.Game.Rulesets.Taiko.UI centreHit.Colour = colours.Pink; } + [Resolved(canBeNull: true)] + private GameplayClock gameplayClock { get; set; } + public bool OnPressed(TaikoAction action) { Drawable target = null; @@ -157,14 +161,16 @@ namespace osu.Game.Rulesets.Taiko.UI target = centreHit; back = centre; - drumSample.Centre?.Play(); + if (gameplayClock?.IsSeeking != true) + drumSample.Centre?.Play(); } else if (action == RimAction) { target = rimHit; back = rim; - drumSample.Rim?.Play(); + if (gameplayClock?.IsSeeking != true) + drumSample.Rim?.Play(); } if (target != null) From 8702a1b5a57ba9a70657b244eaec33797727caf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 May 2020 20:10:51 +0200 Subject: [PATCH 36/39] Fix test scene regression --- .../Visual/Gameplay/TestSceneScrollingHitObjects.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs index 0d15e495e3..20b040dbc3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs @@ -16,6 +16,7 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -221,7 +222,7 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestDrawableControlPoint : DrawableHitObject { public TestDrawableControlPoint(ScrollingDirection direction, double time) - : base(new HitObject { StartTime = time }) + : base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty }) { Origin = Anchor.Centre; @@ -252,7 +253,7 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestDrawableHitObject : DrawableHitObject { public TestDrawableHitObject(double time) - : base(new HitObject { StartTime = time }) + : base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty }) { Origin = Anchor.Custom; OriginPosition = new Vector2(75 / 4.0f); From 24d898c87031a62b21c98812e0ff2939392f7d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 May 2020 21:35:12 +0200 Subject: [PATCH 37/39] Demonstrate failure case in visual test scene --- .../Gameplay/TestSceneScrollingHitObjects.cs | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs index 20b040dbc3..2f15e549f7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs @@ -78,19 +78,18 @@ namespace osu.Game.Tests.Visual.Gameplay } }; - setUpHitObjects(); + hitObjectSpawnDelegate?.Cancel(); }); - private void setUpHitObjects() + private void setUpHitObjects() => AddStep("set up hit objects", () => { scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0))); for (int i = spawn_rate / 2; i <= time_range; i += spawn_rate) addHitObject(Time.Current + i); - hitObjectSpawnDelegate?.Cancel(); hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + time_range), spawn_rate, true); - } + }); private IList testControlPoints => new List { @@ -102,6 +101,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestScrollAlgorithms() { + setUpHitObjects(); + AddStep("constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant)); AddStep("overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping)); AddStep("sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential)); @@ -114,6 +115,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestConstantScrollLifetime() { + setUpHitObjects(); + AddStep("set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant)); // scroll container time range must be less than the rate of spawning hitobjects // otherwise the hitobjects will spawn already partly visible on screen and look wrong @@ -123,14 +126,40 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSequentialScrollLifetime() { + setUpHitObjects(); + AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential)); AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0)); AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current)); } + [Test] + public void TestSlowSequentialScroll() + { + AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential)); + AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range)); + AddStep("add control points", () => addControlPoints( + new List + { + new MultiplierControlPoint { Velocity = 0.1 } + }, + Time.Current + time_range)); + + // All of the hit objects added below should be immediately visible on screen + AddStep("add hit objects", () => + { + for (int i = 0; i < 20; ++i) + { + addHitObject(Time.Current + time_range * (2 + 0.1 * i)); + } + }); + } + [Test] public void TestOverlappingScrollLifetime() { + setUpHitObjects(); + AddStep("set overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping)); AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0)); AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current)); From 4299bd05b4ca268e192a4f4d469f69bed4c6415b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 May 2020 21:39:15 +0200 Subject: [PATCH 38/39] Add test cases for sequential scroll algorithm --- .../ScrollAlgorithms/SequentialScrollTest.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs index 1f0c069f8d..bd578dcbc4 100644 --- a/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs +++ b/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs @@ -29,8 +29,22 @@ namespace osu.Game.Tests.ScrollAlgorithms [Test] public void TestDisplayStartTime() { - // Sequential scroll algorithm approximates the start time - // This should be fixed in the future + // easy cases - time range adjusted for velocity fits within control point duration + Assert.AreEqual(2500, algorithm.GetDisplayStartTime(5000, 0, 2500, 1)); // 5000 - (2500 / 1) + Assert.AreEqual(13750, algorithm.GetDisplayStartTime(15000, 0, 2500, 1)); // 15000 - (2500 / 2) + Assert.AreEqual(20000, algorithm.GetDisplayStartTime(25000, 0, 2500, 1)); // 25000 - (2500 / 0.5) + + // hard case - time range adjusted for velocity exceeds control point duration + + // 1st multiplier point takes 10000 / 2500 = 4 scroll lengths + // 2nd multiplier point takes 10000 / (2500 / 2) = 8 scroll lengths + // 3rd multiplier point takes 2500 / (2500 * 2) = 0.5 scroll lengths up to hitobject start + + // absolute position of the hitobject = 1000 * (4 + 8 + 0.5) = 12500 + // minus one scroll length allowance = 12500 - 1000 = 11500 = 11.5 [scroll lengths] + // therefore the start time lies within the second multiplier point (because 11.5 < 4 + 8) + // its exact time position is = 10000 + 7.5 * (2500 / 2) = 19375 + Assert.AreEqual(19375, algorithm.GetDisplayStartTime(22500, 0, 2500, 1000)); } [Test] From 6f388b731ee97aedebc375370b429539cf8946d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 May 2020 21:41:56 +0200 Subject: [PATCH 39/39] Fix display start time in sequential scroll algorithm --- .../UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs index 41f9ebdb82..0052c877f6 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs @@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength) { - double adjustedTime = TimeAt(-offset, originTime, timeRange, scrollLength); - return adjustedTime - timeRange - 1000; + return TimeAt(-(scrollLength + offset), originTime, timeRange, scrollLength); } public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)