From 651706b10e4ba85bea3faba3ccf70c2736cbf5aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 16:17:20 +0900 Subject: [PATCH 01/16] Account for user/system offsets when deciding on an initial seek time Closes #3043 Remove "AllowLeadIn" flag --- osu.Game/Screens/Play/GameplayClockContainer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index c151e598f7..f6a23575e9 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -26,6 +26,10 @@ namespace osu.Game.Screens.Play private readonly WorkingBeatmap beatmap; private readonly IReadOnlyList mods; + private readonly bool allowLeadIn; + + private readonly double gameplayStartTime; + /// /// The original source (usually a 's track). /// @@ -60,6 +64,8 @@ namespace osu.Game.Screens.Play private readonly FramedOffsetClock platformOffsetClock; + private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) { this.beatmap = beatmap; @@ -93,6 +99,9 @@ namespace osu.Game.Screens.Play userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); + adjustableClock.Seek(Math.Min(0, gameplayStartTime - totalOffset - (allowLeadIn ? beatmap.BeatmapInfo.AudioLeadIn : 0))); + adjustableClock.ProcessFrame(); + UserPlaybackRate.ValueChanged += _ => updateRate(); Seek(Math.Min(-beatmap.BeatmapInfo.AudioLeadIn, gameplayStartTime)); From d1d1c4ee7a5df46e20330b28b293dc700c9da7e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 16:13:58 +0900 Subject: [PATCH 02/16] Add lead-in tests --- .../Visual/Gameplay/TestCaseLeadIn.cs | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs new file mode 100644 index 0000000000..0186ba1da1 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs @@ -0,0 +1,88 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestCaseLeadIn : RateAdjustedBeatmapTestCase + { + private Ruleset ruleset; + + private LeadInPlayer player; + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Depth = int.MaxValue + }); + + ruleset = rulesets.AvailableRulesets.First().CreateInstance(); + } + + [Test] + public void TestShortLeadIn() + { + AddStep("create player", () => loadPlayerWithBeatmap(new TestBeatmap(ruleset.RulesetInfo) { BeatmapInfo = { AudioLeadIn = 1000 } })); + AddUntilStep("correct lead-in", () => player.FirstFrameClockTime == 0); + } + + [Test] + public void TestLongLeadIn() + { + AddStep("create player", () => loadPlayerWithBeatmap(new TestBeatmap(ruleset.RulesetInfo) { BeatmapInfo = { AudioLeadIn = 10000 } })); + AddUntilStep("correct lead-in", () => player.FirstFrameClockTime == player.GameplayStartTime - 10000); + } + + private void loadPlayerWithBeatmap(IBeatmap beatmap) + { + Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); + + LoadScreen(player = new LeadInPlayer + { + AllowPause = false, + AllowResults = false, + }); + } + + private class LeadInPlayer : Player + { + public double? FirstFrameClockTime; + + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + + public double GameplayStartTime => DrawableRuleset.GameplayStartTime; + + public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime; + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + if (!FirstFrameClockTime.HasValue) + { + FirstFrameClockTime = GameplayClockContainer.GameplayClock.CurrentTime; + AddInternal(new OsuSpriteText + { + Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} " + + $"LeadInTime: {Beatmap.Value.BeatmapInfo.AudioLeadIn} " + + $"FirstFrameClockTime: {FirstFrameClockTime}" + }); + } + } + } + } +} From e03a664970e39158396b478dfef1f6aa7e0574b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 16:18:15 +0900 Subject: [PATCH 03/16] Fix lead-in logic to match stable Also adds storyboard event priority support. --- osu.Game/Screens/Play/GameplayClock.cs | 1 - osu.Game/Screens/Play/GameplayClockContainer.cs | 8 +++++++- osu.Game/Storyboards/Storyboard.cs | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index b1948d02d5..6c9dacfc39 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -34,7 +34,6 @@ namespace osu.Game.Screens.Play public void ProcessFrame() { // we do not want to process the underlying clock. - // this is handled by PauseContainer. } public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index f6a23575e9..7abdc8ef66 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -99,7 +99,13 @@ namespace osu.Game.Screens.Play userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); - adjustableClock.Seek(Math.Min(0, gameplayStartTime - totalOffset - (allowLeadIn ? beatmap.BeatmapInfo.AudioLeadIn : 0))); + double startTime = -beatmap.BeatmapInfo.AudioLeadIn; + + startTime = Math.Min(startTime, beatmap.Storyboard.FirstEventTime); + startTime = Math.Min(startTime, gameplayStartTime); + startTime = Math.Min(startTime, 0); + + Seek(startTime); adjustableClock.ProcessFrame(); UserPlaybackRate.ValueChanged += _ => updateRate(); diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 3d988c5fe3..401a7ce25a 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -17,6 +17,8 @@ namespace osu.Game.Storyboards public bool HasDrawable => Layers.Any(l => l.Elements.Any(e => e.IsDrawable)); + public double FirstEventTime => Layers.Min(l => l.Elements.FirstOrDefault()?.StartTime ?? 0); + public Storyboard() { layers.Add("Background", new StoryboardLayer("Background", 3)); From a2fbcb2bd39e756503d6e98a9cf25fb57dba021e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 May 2019 15:58:46 +0900 Subject: [PATCH 04/16] Fix rebase regressions --- osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs | 14 ++++++++------ osu.Game/Screens/Play/GameplayClockContainer.cs | 6 ------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs index 0186ba1da1..33f1e4062f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs @@ -15,7 +15,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay { - public class TestCaseLeadIn : RateAdjustedBeatmapTestCase + public class TestCaseLeadIn : RateAdjustedBeatmapTestScene { private Ruleset ruleset; @@ -52,15 +52,16 @@ namespace osu.Game.Tests.Visual.Gameplay { Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); - LoadScreen(player = new LeadInPlayer - { - AllowPause = false, - AllowResults = false, - }); + LoadScreen(player = new LeadInPlayer()); } private class LeadInPlayer : Player { + public LeadInPlayer() + : base(false, false) + { + } + public double? FirstFrameClockTime; public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; @@ -72,6 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); + if (!FirstFrameClockTime.HasValue) { FirstFrameClockTime = GameplayClockContainer.GameplayClock.CurrentTime; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 7abdc8ef66..d718424b29 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -26,10 +26,6 @@ namespace osu.Game.Screens.Play private readonly WorkingBeatmap beatmap; private readonly IReadOnlyList mods; - private readonly bool allowLeadIn; - - private readonly double gameplayStartTime; - /// /// The original source (usually a 's track). /// @@ -91,8 +87,6 @@ namespace osu.Game.Screens.Play GameplayClock.IsPaused.BindTo(IsPaused); } - private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { From c7d0b88854a66cfacab8ac91c2e885088a606ca3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2019 14:15:09 +0900 Subject: [PATCH 05/16] Update test scene --- .../Visual/Gameplay/{TestCaseLeadIn.cs => TestSceneLeadIn.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Gameplay/{TestCaseLeadIn.cs => TestSceneLeadIn.cs} (97%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs similarity index 97% rename from osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 41cd80ed8c..3522c2265a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -15,7 +15,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay { - public class TestCaseLeadIn : RateAdjustedBeatmapTestScene + public class TestSceneLeadIn : RateAdjustedBeatmapTestScene { private Ruleset ruleset; From dbfec215e7acdf716f4c11c42f3ab8bd6c7711d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2019 14:47:28 +0900 Subject: [PATCH 06/16] Improve test quality --- .../Visual/Gameplay/TestSceneLeadIn.cs | 50 ++++++++----------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 3522c2265a..addf7c3b28 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -1,61 +1,55 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; -using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneLeadIn : RateAdjustedBeatmapTestScene { - private Ruleset ruleset; - private LeadInPlayer player; - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); - - ruleset = rulesets.AvailableRulesets.First().CreateInstance(); - } - [Test] public void TestShortLeadIn() { - AddStep("create player", () => loadPlayerWithBeatmap(new TestBeatmap(ruleset.RulesetInfo) { BeatmapInfo = { AudioLeadIn = 1000 } })); - AddUntilStep("correct lead-in", () => player.FirstFrameClockTime == 0); + loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = { AudioLeadIn = 1000 } + }); + + AddUntilStep("player loaded", () => player.IsLoaded && player.Alpha == 1); + + AddAssert("correct lead-in", () => player.FirstFrameClockTime == 0); } [Test] public void TestLongLeadIn() { - AddStep("create player", () => loadPlayerWithBeatmap(new TestBeatmap(ruleset.RulesetInfo) { BeatmapInfo = { AudioLeadIn = 10000 } })); - AddUntilStep("correct lead-in", () => player.FirstFrameClockTime == player.GameplayStartTime - 10000); + loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = { AudioLeadIn = 10000 } + }); + + AddAssert("correct lead-in", () => player.FirstFrameClockTime == player.GameplayStartTime - 10000); } private void loadPlayerWithBeatmap(IBeatmap beatmap) { - Beatmap.Value = new TestWorkingBeatmap(beatmap); + AddStep("create player", () => + { + Beatmap.Value = CreateWorkingBeatmap(beatmap); + LoadScreen(player = new LeadInPlayer()); + }); - LoadScreen(player = new LeadInPlayer()); + AddUntilStep("player loaded", () => player.IsLoaded && player.Alpha == 1); } - private class LeadInPlayer : Player + private class LeadInPlayer : TestPlayer { public LeadInPlayer() : base(false, false) From 8369be90f20842c6b9a6aaf4eba5a4671c0c2915 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 15:19:06 +0900 Subject: [PATCH 07/16] Allow skip button to actuate more than once --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 13 +--------- .../Screens/Play/GameplayClockContainer.cs | 26 ++++++++++++++++--- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/SkipOverlay.cs | 20 +++----------- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index b152c21454..0d62d9a520 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay { skip = new SkipOverlay(6000) { - RequestSeek = _ => requestCount++ + RequestSkip = () => requestCount++ } }, }; @@ -60,17 +60,6 @@ namespace osu.Game.Tests.Visual.Gameplay checkRequestCount(1); } - [Test] - public void TestClickOnlyActuatesOnce() - { - AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - checkRequestCount(1); - } - [Test] public void TestDoesntFadeOnMouseDown() { diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 671711f5a4..e1f4d8fa94 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -89,6 +89,11 @@ namespace osu.Game.Screens.Play private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + /// + /// Duration before gameplay start time required before skip button displays. + /// + public const double MINIMUM_SKIP_TIME = 1000; + private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); [BackgroundDependencyLoader] @@ -104,11 +109,9 @@ namespace osu.Game.Screens.Play startTime = Math.Min(startTime, 0); Seek(startTime); + adjustableClock.ProcessFrame(); - UserPlaybackRate.ValueChanged += _ => updateRate(); - - Seek(Math.Min(-beatmap.BeatmapInfo.AudioLeadIn, gameplayStartTime)); } public void Restart() @@ -139,6 +142,23 @@ namespace osu.Game.Screens.Play this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); } + /// + /// Skip forwardto the next valid skip point. + /// + public void Skip() + { + if (GameplayClock.CurrentTime > gameplayStartTime - MINIMUM_SKIP_TIME) + return; + + double skipTarget = gameplayStartTime - MINIMUM_SKIP_TIME; + + if (GameplayClock.CurrentTime < 0 && skipTarget > 6000) + // double skip exception for storyboards with very long intros + skipTarget = 0; + + Seek(skipTarget); + } + /// /// Seek to a specific time in gameplay. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a9b0649fab..d6488dc209 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -203,7 +203,7 @@ namespace osu.Game.Screens.Play }, new SkipOverlay(DrawableRuleset.GameplayStartTime) { - RequestSeek = GameplayClockContainer.Seek + RequestSkip = GameplayClockContainer.Skip }, FailOverlay = new FailOverlay { diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 835867fe62..3abe61d1a9 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play { private readonly double startTime; - public Action RequestSeek; + public Action RequestSkip; private Button button; private Box remainingTimeBox; @@ -90,11 +90,6 @@ namespace osu.Game.Screens.Play }; } - /// - /// Duration before gameplay start time required before skip button displays. - /// - private const double skip_buffer = 1000; - private const double fade_time = 300; private double beginFadeTime => startTime - fade_time; @@ -104,7 +99,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); // skip is not required if there is no extra "empty" time to skip. - if (Clock.CurrentTime > beginFadeTime - skip_buffer) + if (Clock.CurrentTime > beginFadeTime - GameplayClockContainer.MINIMUM_SKIP_TIME) { Alpha = 0; Expire(); @@ -115,10 +110,9 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(beginFadeTime)) this.FadeOut(fade_time); - button.Action = () => RequestSeek?.Invoke(beginFadeTime); + button.Action = () => RequestSkip?.Invoke(); displayTime = Time.Current; - Expire(); } @@ -335,13 +329,7 @@ namespace osu.Game.Screens.Play box.FlashColour(Color4.White, 500, Easing.OutQuint); aspect.ScaleTo(1.2f, 2000, Easing.OutQuint); - bool result = base.OnClick(e); - - // for now, let's disable the skip button after the first press. - // this will likely need to be contextual in the future (bound from external components). - Enabled.Value = false; - - return result; + return base.OnClick(e); } } } From 586e31efc256964b722de2112d55d367ee88ea56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 15:56:08 +0900 Subject: [PATCH 08/16] Update tests --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index addf7c3b28..9275dc636c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu; @@ -22,9 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay BeatmapInfo = { AudioLeadIn = 1000 } }); - AddUntilStep("player loaded", () => player.IsLoaded && player.Alpha == 1); - - AddAssert("correct lead-in", () => player.FirstFrameClockTime == 0); + AddAssert("correct lead-in", () => Precision.AlmostEquals(player.FirstFrameClockTime.Value, 0, 100)); } [Test] @@ -35,7 +34,7 @@ namespace osu.Game.Tests.Visual.Gameplay BeatmapInfo = { AudioLeadIn = 10000 } }); - AddAssert("correct lead-in", () => player.FirstFrameClockTime == player.GameplayStartTime - 10000); + AddAssert("correct lead-in", () => Precision.AlmostEquals(player.FirstFrameClockTime.Value, player.GameplayStartTime - 10000, 100)); } private void loadPlayerWithBeatmap(IBeatmap beatmap) From 2dd2e3d861706d22991174ed0e068f7f3c738ae9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 16:55:18 +0900 Subject: [PATCH 09/16] Add correct AudioLeadIn support --- osu.Game/Screens/Play/GameplayClockContainer.cs | 17 +++++++++++++---- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index e1f4d8fa94..bc6f649492 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -41,6 +41,8 @@ namespace osu.Game.Screens.Play private readonly double gameplayStartTime; + private readonly double firstHitObjectTime; + public readonly Bindable UserPlaybackRate = new BindableDouble(1) { Default = 1, @@ -61,11 +63,12 @@ namespace osu.Game.Screens.Play private readonly FramedOffsetClock platformOffsetClock; - public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) + public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime, double firstHitObjectTime) { this.beatmap = beatmap; this.mods = mods; this.gameplayStartTime = gameplayStartTime; + this.firstHitObjectTime = firstHitObjectTime; RelativeSizeAxes = Axes.Both; @@ -102,11 +105,17 @@ namespace osu.Game.Screens.Play userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); - double startTime = -beatmap.BeatmapInfo.AudioLeadIn; + // sane default provided by ruleset. + double startTime = Math.Min(0, gameplayStartTime); + // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. + // this is commonly used to display an intro before the audio track start. startTime = Math.Min(startTime, beatmap.Storyboard.FirstEventTime); - startTime = Math.Min(startTime, gameplayStartTime); - startTime = Math.Min(startTime, 0); + + // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. + // this is not available as an option in the live editor but can still be applied via .osu editing. + if (beatmap.BeatmapInfo.AudioLeadIn > 0) + startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); Seek(startTime); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d6488dc209..cb71693312 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Play if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime, DrawableRuleset.Objects.First().StartTime); addUnderlayComponents(GameplayClockContainer); addGameplayComponents(GameplayClockContainer, working); From 29d23749285dfc0773a152ee81536a061cc0c9ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 17:12:29 +0900 Subject: [PATCH 10/16] Add back skip button actuation count tests --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 51 +++++++++++++++++-- osu.Game/Screens/Play/SkipOverlay.cs | 2 + 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 0d62d9a520..1b4b0b4dcc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -18,27 +18,41 @@ namespace osu.Game.Tests.Visual.Gameplay private SkipOverlay skip; private int requestCount; + private FramedOffsetClock offsetClock; + private StopwatchClock stopwatchClock; + + private double increment; + [SetUp] public void SetUp() => Schedule(() => { requestCount = 0; + increment = 6000; + Child = new Container { RelativeSizeAxes = Axes.Both, - Clock = new FramedOffsetClock(Clock) - { - Offset = -Clock.CurrentTime, - }, + Clock = offsetClock = new FramedOffsetClock(stopwatchClock = new StopwatchClock(true)), Children = new Drawable[] { skip = new SkipOverlay(6000) { - RequestSkip = () => requestCount++ + RequestSkip = () => + { + requestCount++; + offsetClock.Offset += increment; + } } }, }; }); + protected override void Update() + { + if (stopwatchClock != null) + stopwatchClock.Rate = Clock.Rate; + } + [Test] public void TestFadeOnIdle() { @@ -60,6 +74,33 @@ namespace osu.Game.Tests.Visual.Gameplay checkRequestCount(1); } + [Test] + public void TestClickOnlyActuatesOnce() + { + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); + AddStep("click", () => + { + increment = 6000 - offsetClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2; + InputManager.Click(MouseButton.Left); + }); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkRequestCount(1); + } + + [Test] + public void TestClickOnlyActuatesMultipleTimes() + { + AddStep("set increment lower", () => increment = 3000); + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkRequestCount(2); + } + [Test] public void TestDoesntFadeOnMouseDown() { diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 3abe61d1a9..2c6b33be39 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -124,6 +124,8 @@ namespace osu.Game.Screens.Play { base.Update(); remainingTimeBox.ResizeWidthTo((float)Math.Max(0, 1 - (Time.Current - displayTime) / (beginFadeTime - displayTime)), 120, Easing.OutQuint); + + button.Enabled.Value = Time.Current < startTime - GameplayClockContainer.MINIMUM_SKIP_TIME; } protected override bool OnMouseMove(MouseMoveEvent e) From 4e53bca8dd1e8ad8392a13010af04a9e42b69e50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 18:21:49 +0900 Subject: [PATCH 11/16] Simplify tests --- .../Visual/Gameplay/TestSceneLeadIn.cs | 31 +++++++++++-------- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 9275dc636c..6434387f51 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; +using System.Linq; using NUnit.Framework; using osu.Framework.MathUtils; using osu.Game.Beatmaps; @@ -15,26 +17,26 @@ namespace osu.Game.Tests.Visual.Gameplay { private LeadInPlayer player; - [Test] - public void TestShortLeadIn() + private const double lenience_ms = 10; + + private const double first_hit_object = 2170; + + [TestCase(1000, 0)] + [TestCase(2000, 0)] + [TestCase(3000, first_hit_object - 3000)] + [TestCase(10000, first_hit_object - 10000)] + public void TestLeadInProducesCorrectStartTime(double leadIn, double expectedStartTime) { loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) { - BeatmapInfo = { AudioLeadIn = 1000 } + BeatmapInfo = { AudioLeadIn = leadIn } }); - AddAssert("correct lead-in", () => Precision.AlmostEquals(player.FirstFrameClockTime.Value, 0, 100)); - } - - [Test] - public void TestLongLeadIn() - { - loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) + AddAssert($"first frame is {expectedStartTime}", () => { - BeatmapInfo = { AudioLeadIn = 10000 } + Debug.Assert(player.FirstFrameClockTime != null); + return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms); }); - - AddAssert("correct lead-in", () => Precision.AlmostEquals(player.FirstFrameClockTime.Value, player.GameplayStartTime - 10000, 100)); } private void loadPlayerWithBeatmap(IBeatmap beatmap) @@ -61,6 +63,8 @@ namespace osu.Game.Tests.Visual.Gameplay public double GameplayStartTime => DrawableRuleset.GameplayStartTime; + public double FirstHitObjectTime => DrawableRuleset.Objects.First().StartTime; + public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime; protected override void UpdateAfterChildren() @@ -73,6 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddInternal(new OsuSpriteText { Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} " + + $"FirstHitObjectTime: {FirstHitObjectTime} " + $"LeadInTime: {Beatmap.Value.BeatmapInfo.AudioLeadIn} " + $"FirstFrameClockTime: {FirstFrameClockTime}" }); diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 198046df4f..6e82c465dc 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -76,7 +76,7 @@ namespace osu.Game.Beatmaps public string MD5Hash { get; set; } // General - public int AudioLeadIn { get; set; } + public double AudioLeadIn { get; set; } public bool Countdown { get; set; } = true; public float StackLeniency { get; set; } = 0.7f; public bool SpecialStyle { get; set; } From 8d1b11d4bd86b644c683bd74eac0dc893f61823a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 18:50:54 +0900 Subject: [PATCH 12/16] Add storyboard lead-in tests --- .../TestSceneSkinFallbacks.cs | 7 +++-- .../TestSceneSpinnerRotation.cs | 5 ++-- .../Visual/Gameplay/TestSceneAutoplay.cs | 5 ++-- .../Gameplay/TestSceneGameplayRewinding.cs | 5 ++-- .../Visual/Gameplay/TestSceneLeadIn.cs | 29 +++++++++++++++++-- .../TestScenePlayerReferenceLeaking.cs | 5 ++-- .../Drawables/DrawableStoryboardSprite.cs | 2 +- osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 10 +++++-- osu.Game/Tests/Visual/OsuTestScene.cs | 14 +++++---- 9 files changed, 60 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 02c65db6ad..7ca311118e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Screens.Play; using osu.Game.Skinning; +using osu.Game.Storyboards; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests @@ -75,14 +76,14 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => new CustomSkinWorkingBeatmap(beatmap, Clock, audio, testBeatmapSkin); + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, audio, testBeatmapSkin); public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap { private readonly ISkinSource skin; - public CustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) - : base(beatmap, frameBasedClock, audio) + public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) + : base(beatmap, storyboard, frameBasedClock, audio) { this.skin = skin; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index cded7f0e95..d0ce0c33c2 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -15,6 +15,7 @@ using osu.Game.Tests.Visual; using osuTK; using System.Collections.Generic; using System.Linq; +using osu.Game.Storyboards; using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; namespace osu.Game.Rulesets.Osu.Tests @@ -28,9 +29,9 @@ namespace osu.Game.Rulesets.Osu.Tests protected override bool Autoplay => true; - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { - var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); track = (TrackVirtualManual)working.Track; return working; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index f94071a7a9..5ee109e3dd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -7,6 +7,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; +using osu.Game.Storyboards; namespace osu.Game.Tests.Visual.Gameplay { @@ -29,9 +30,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); } - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { - var working = base.CreateWorkingBeatmap(beatmap); + var working = base.CreateWorkingBeatmap(beatmap, storyboard); track = (ClockBackedTestWorkingBeatmap.TrackVirtualManual)working.Track; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index ffc025a942..b2b58a63fb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; +using osu.Game.Storyboards; using osuTK; namespace osu.Game.Tests.Visual.Gameplay @@ -35,9 +36,9 @@ namespace osu.Game.Tests.Visual.Gameplay private Track track; - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { - var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); track = working.Track; return working; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 6434387f51..0150c6ea74 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -4,12 +4,15 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; +using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps; +using osuTK; namespace osu.Game.Tests.Visual.Gameplay { @@ -39,11 +42,33 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - private void loadPlayerWithBeatmap(IBeatmap beatmap) + [TestCase(1000, 0)] + [TestCase(0, 0)] + [TestCase(-1000, -1000)] + [TestCase(-10000, -10000)] + public void TestStoryboardProducesCorrectStartTime(double firstStoryboardEvent, double expectedStartTime) + { + var storyboard = new Storyboard(); + + var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); + sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1); + + storyboard.GetLayer("Background").Add(sprite); + + loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard); + + AddAssert($"first frame is {expectedStartTime}", () => + { + Debug.Assert(player.FirstFrameClockTime != null); + return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms); + }); + } + + private void loadPlayerWithBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { AddStep("create player", () => { - Beatmap.Value = CreateWorkingBeatmap(beatmap); + Beatmap.Value = CreateWorkingBeatmap(beatmap, storyboard); LoadScreen(player = new LeadInPlayer()); }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs index 65b56319e8..4d701f56a9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs @@ -6,6 +6,7 @@ using osu.Framework.Lists; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Screens.Play; +using osu.Game.Storyboards; namespace osu.Game.Tests.Visual.Gameplay { @@ -42,9 +43,9 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { - var working = base.CreateWorkingBeatmap(beatmap); + var working = base.CreateWorkingBeatmap(beatmap, storyboard); workingWeakReferences.Add(working); return working; } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 5f1f5ddacb..3a117d1713 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -66,7 +66,7 @@ namespace osu.Game.Storyboards.Drawables [BackgroundDependencyLoader] private void load(IBindable beatmap, TextureStore textureStore) { - var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; if (path == null) return; diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 0d9f4f51be..871d8ee3f1 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -5,25 +5,31 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Video; using osu.Game.Beatmaps; +using osu.Game.Storyboards; namespace osu.Game.Tests.Beatmaps { public class TestWorkingBeatmap : WorkingBeatmap { private readonly IBeatmap beatmap; + private readonly Storyboard storyboard; /// /// Create an instance which provides the when requested. /// - /// The beatmap - public TestWorkingBeatmap(IBeatmap beatmap) + /// The beatmap. + /// An optional storyboard. + public TestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) : base(beatmap.BeatmapInfo, null) { this.beatmap = beatmap; + this.storyboard = storyboard; } protected override IBeatmap GetBeatmap() => beatmap; + protected override Storyboard GetStoryboard() => storyboard ?? base.GetStoryboard(); + protected override Texture GetBackground() => null; protected override VideoSprite GetVideo() => null; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 13e08c7d22..be67c2fe23 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -21,6 +21,7 @@ using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual @@ -119,10 +120,10 @@ namespace osu.Game.Tests.Visual protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset); protected WorkingBeatmap CreateWorkingBeatmap(RulesetInfo ruleset) => - CreateWorkingBeatmap(CreateBeatmap(ruleset)); + CreateWorkingBeatmap(CreateBeatmap(ruleset), null); - protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => - new ClockBackedTestWorkingBeatmap(beatmap, Clock, audio); + protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => + new ClockBackedTestWorkingBeatmap(beatmap, storyboard, Clock, audio); [BackgroundDependencyLoader] private void load(RulesetStore rulesets) @@ -168,7 +169,7 @@ namespace osu.Game.Tests.Visual /// A clock which should be used instead of a stopwatch for virtual time progression. /// Audio manager. Required if a reference clock isn't provided. public ClockBackedTestWorkingBeatmap(RulesetInfo ruleset, IFrameBasedClock referenceClock, AudioManager audio) - : this(new TestBeatmap(ruleset), referenceClock, audio) + : this(new TestBeatmap(ruleset), null, referenceClock, audio) { } @@ -176,11 +177,12 @@ namespace osu.Game.Tests.Visual /// Create an instance which provides the when requested. /// /// The beatmap + /// The storyboard. /// An optional clock which should be used instead of a stopwatch for virtual time progression. /// Audio manager. Required if a reference clock isn't provided. /// The length of the returned virtual track. - public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock referenceClock, AudioManager audio, double length = 60000) - : base(beatmap) + public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, double length = 60000) + : base(beatmap, storyboard) { if (referenceClock != null) { From 46a94821d44129975336136556b2b18e9e791e1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 18:57:19 +0900 Subject: [PATCH 13/16] Add support for consecutive skips --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 42 ++++++++++++++++--- .../Screens/Play/GameplayClockContainer.cs | 22 ++++++++++ osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/SkipOverlay.cs | 22 +++------- 4 files changed, 65 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index b152c21454..1b4b0b4dcc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -18,27 +18,41 @@ namespace osu.Game.Tests.Visual.Gameplay private SkipOverlay skip; private int requestCount; + private FramedOffsetClock offsetClock; + private StopwatchClock stopwatchClock; + + private double increment; + [SetUp] public void SetUp() => Schedule(() => { requestCount = 0; + increment = 6000; + Child = new Container { RelativeSizeAxes = Axes.Both, - Clock = new FramedOffsetClock(Clock) - { - Offset = -Clock.CurrentTime, - }, + Clock = offsetClock = new FramedOffsetClock(stopwatchClock = new StopwatchClock(true)), Children = new Drawable[] { skip = new SkipOverlay(6000) { - RequestSeek = _ => requestCount++ + RequestSkip = () => + { + requestCount++; + offsetClock.Offset += increment; + } } }, }; }); + protected override void Update() + { + if (stopwatchClock != null) + stopwatchClock.Rate = Clock.Rate; + } + [Test] public void TestFadeOnIdle() { @@ -64,13 +78,29 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestClickOnlyActuatesOnce() { AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); - AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => + { + increment = 6000 - offsetClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2; + InputManager.Click(MouseButton.Left); + }); AddStep("click", () => InputManager.Click(MouseButton.Left)); AddStep("click", () => InputManager.Click(MouseButton.Left)); AddStep("click", () => InputManager.Click(MouseButton.Left)); checkRequestCount(1); } + [Test] + public void TestClickOnlyActuatesMultipleTimes() + { + AddStep("set increment lower", () => increment = 3000); + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkRequestCount(2); + } + [Test] public void TestDoesntFadeOnMouseDown() { diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 2f2028ff53..6903ccf06d 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -89,6 +89,11 @@ namespace osu.Game.Screens.Play private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + /// + /// Duration before gameplay start time required before skip button displays. + /// + public const double MINIMUM_SKIP_TIME = 1000; + private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); [BackgroundDependencyLoader] @@ -130,6 +135,23 @@ namespace osu.Game.Screens.Play this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); } + /// + /// Skip forward to the next valid skip point. + /// + public void Skip() + { + if (GameplayClock.CurrentTime > gameplayStartTime - MINIMUM_SKIP_TIME) + return; + + double skipTarget = gameplayStartTime - MINIMUM_SKIP_TIME; + + if (GameplayClock.CurrentTime < 0 && skipTarget > 6000) + // double skip exception for storyboards with very long intros + skipTarget = 0; + + Seek(skipTarget); + } + /// /// Seek to a specific time in gameplay. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a9b0649fab..d6488dc209 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -203,7 +203,7 @@ namespace osu.Game.Screens.Play }, new SkipOverlay(DrawableRuleset.GameplayStartTime) { - RequestSeek = GameplayClockContainer.Seek + RequestSkip = GameplayClockContainer.Skip }, FailOverlay = new FailOverlay { diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 835867fe62..2c6b33be39 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play { private readonly double startTime; - public Action RequestSeek; + public Action RequestSkip; private Button button; private Box remainingTimeBox; @@ -90,11 +90,6 @@ namespace osu.Game.Screens.Play }; } - /// - /// Duration before gameplay start time required before skip button displays. - /// - private const double skip_buffer = 1000; - private const double fade_time = 300; private double beginFadeTime => startTime - fade_time; @@ -104,7 +99,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); // skip is not required if there is no extra "empty" time to skip. - if (Clock.CurrentTime > beginFadeTime - skip_buffer) + if (Clock.CurrentTime > beginFadeTime - GameplayClockContainer.MINIMUM_SKIP_TIME) { Alpha = 0; Expire(); @@ -115,10 +110,9 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(beginFadeTime)) this.FadeOut(fade_time); - button.Action = () => RequestSeek?.Invoke(beginFadeTime); + button.Action = () => RequestSkip?.Invoke(); displayTime = Time.Current; - Expire(); } @@ -130,6 +124,8 @@ namespace osu.Game.Screens.Play { base.Update(); remainingTimeBox.ResizeWidthTo((float)Math.Max(0, 1 - (Time.Current - displayTime) / (beginFadeTime - displayTime)), 120, Easing.OutQuint); + + button.Enabled.Value = Time.Current < startTime - GameplayClockContainer.MINIMUM_SKIP_TIME; } protected override bool OnMouseMove(MouseMoveEvent e) @@ -335,13 +331,7 @@ namespace osu.Game.Screens.Play box.FlashColour(Color4.White, 500, Easing.OutQuint); aspect.ScaleTo(1.2f, 2000, Easing.OutQuint); - bool result = base.OnClick(e); - - // for now, let's disable the skip button after the first press. - // this will likely need to be contextual in the future (bound from external components). - Enabled.Value = false; - - return result; + return base.OnClick(e); } } } From bd6831624a58802f022eccc616d90f977c9155e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 19:51:22 +0900 Subject: [PATCH 14/16] Decouple skip button animations from gameplay clock --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 32 +++++++-------- osu.Game/Screens/Play/SkipOverlay.cs | 40 +++++++++---------- 2 files changed, 33 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 1b4b0b4dcc..a21f55e361 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -4,8 +4,8 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osuTK; using osuTK.Input; @@ -18,40 +18,38 @@ namespace osu.Game.Tests.Visual.Gameplay private SkipOverlay skip; private int requestCount; - private FramedOffsetClock offsetClock; - private StopwatchClock stopwatchClock; - private double increment; + private GameplayClockContainer gameplayClockContainer; + private GameplayClock gameplayClock; + + private const double skip_time = 6000; + [SetUp] public void SetUp() => Schedule(() => { requestCount = 0; - increment = 6000; + increment = skip_time; - Child = new Container + Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new Mod[] { }, 0) { RelativeSizeAxes = Axes.Both, - Clock = offsetClock = new FramedOffsetClock(stopwatchClock = new StopwatchClock(true)), Children = new Drawable[] { - skip = new SkipOverlay(6000) + skip = new SkipOverlay(skip_time) { RequestSkip = () => { requestCount++; - offsetClock.Offset += increment; + gameplayClockContainer.Seek(gameplayClock.CurrentTime + increment); } } }, }; - }); - protected override void Update() - { - if (stopwatchClock != null) - stopwatchClock.Rate = Clock.Rate; - } + gameplayClockContainer.Start(); + gameplayClock = gameplayClockContainer.GameplayClock; + }); [Test] public void TestFadeOnIdle() @@ -80,7 +78,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddStep("click", () => { - increment = 6000 - offsetClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2; + increment = skip_time - gameplayClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2; InputManager.Click(MouseButton.Left); }); AddStep("click", () => InputManager.Click(MouseButton.Left)); diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 2c6b33be39..1a5ed20953 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -19,6 +19,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.MathUtils; using osu.Game.Input.Bindings; namespace osu.Game.Screens.Play @@ -35,6 +36,9 @@ namespace osu.Game.Screens.Play private FadeContainer fadeContainer; private double displayTime; + [Resolved] + private GameplayClock gameplayClock { get; set; } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; /// @@ -45,8 +49,6 @@ namespace osu.Game.Screens.Play { this.startTime = startTime; - Show(); - RelativePositionAxes = Axes.Both; RelativeSizeAxes = Axes.X; @@ -57,13 +59,8 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, GameplayClock clock) + private void load(OsuColour colours) { - var baseClock = Clock; - - if (clock != null) - Clock = clock; - Children = new Drawable[] { fadeContainer = new FadeContainer @@ -73,7 +70,6 @@ namespace osu.Game.Screens.Play { button = new Button { - Clock = baseClock, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -92,40 +88,40 @@ namespace osu.Game.Screens.Play private const double fade_time = 300; - private double beginFadeTime => startTime - fade_time; + private double fadeOutBeginTime => startTime - GameplayClockContainer.MINIMUM_SKIP_TIME; protected override void LoadComplete() { base.LoadComplete(); // skip is not required if there is no extra "empty" time to skip. - if (Clock.CurrentTime > beginFadeTime - GameplayClockContainer.MINIMUM_SKIP_TIME) + // we may need to remove this if rewinding before the initial player load position becomes a thing. + if (fadeOutBeginTime < gameplayClock.CurrentTime) { - Alpha = 0; Expire(); return; } - this.FadeInFromZero(fade_time); - using (BeginAbsoluteSequence(beginFadeTime)) - this.FadeOut(fade_time); - button.Action = () => RequestSkip?.Invoke(); + displayTime = gameplayClock.CurrentTime; - displayTime = Time.Current; - Expire(); + Show(); } - protected override void PopIn() => this.FadeIn(); + protected override void PopIn() => this.FadeIn(fade_time); - protected override void PopOut() => this.FadeOut(); + protected override void PopOut() => this.FadeOut(fade_time); protected override void Update() { base.Update(); - remainingTimeBox.ResizeWidthTo((float)Math.Max(0, 1 - (Time.Current - displayTime) / (beginFadeTime - displayTime)), 120, Easing.OutQuint); - button.Enabled.Value = Time.Current < startTime - GameplayClockContainer.MINIMUM_SKIP_TIME; + var progress = Math.Max(0, 1 - (gameplayClock.CurrentTime - displayTime) / (fadeOutBeginTime - displayTime)); + + 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; } protected override bool OnMouseMove(MouseMoveEvent e) From 71a64da566a685cce2ad929d989afab589c2b60e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 22:07:37 +0900 Subject: [PATCH 15/16] Fix test regressions --- 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 a21f55e361..875e7b9758 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -104,7 +104,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 disapper", () => !skip.IsAlive); + AddUntilStep("wait for overlay disappear", () => !skip.IsPresent); AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0); AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left)); checkRequestCount(0); From d8260f4a6511ce6118507f114e06eed4cd0bc229 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 10:51:49 +0900 Subject: [PATCH 16/16] Reduce carousel scroll motion on initial display --- osu.Game/Screens/Select/BeatmapCarousel.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 538656a5fa..17736e7819 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -640,10 +640,19 @@ namespace osu.Game.Screens.Select itemsCache.Validate(); } + private bool firstScroll = true; + private void updateScrollPosition() { if (scrollTarget != null) { + if (firstScroll) + { + // reduce movement when first displaying the carousel. + scroll.ScrollTo(scrollTarget.Value - 200, false); + firstScroll = false; + } + scroll.ScrollTo(scrollTarget.Value); scrollPositionCache.Validate(); }