diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index dbbedf37ae..a38089e023 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -29,6 +29,7 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Utils; using osuTK.Input; +using SkipOverlay = osu.Game.Screens.Play.SkipOverlay; namespace osu.Game.Tests.Visual.Gameplay { @@ -98,7 +99,13 @@ namespace osu.Game.Tests.Visual.Gameplay private void prepareBeatmap() { var workingBeatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + + // Add intro time to test quick retry skipping (TestQuickRetry). + workingBeatmap.BeatmapInfo.AudioLeadIn = 60000; + + // Turn on epilepsy warning to test warning display (TestEpilepsyWarning). workingBeatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning; + Beatmap.Value = workingBeatmap; foreach (var mod in SelectedMods.Value.OfType()) @@ -400,6 +407,37 @@ namespace osu.Game.Tests.Visual.Gameplay }); } + [Test] + public void TestQuickRetry() + { + TestPlayer getCurrentPlayer() => loader.CurrentPlayer as TestPlayer; + bool checkSkipButtonVisible() => player.ChildrenOfType().FirstOrDefault()?.IsButtonVisible == true; + + TestPlayer previousPlayer = null; + + AddStep("load dummy beatmap", () => resetPlayer(false)); + + AddUntilStep("wait for current", () => getCurrentPlayer()?.IsCurrentScreen() == true); + AddStep("store previous player", () => previousPlayer = getCurrentPlayer()); + + AddStep("Restart map normally", () => getCurrentPlayer().Restart()); + AddUntilStep("wait for load", () => getCurrentPlayer()?.LoadedBeatmapSuccessfully == true); + + AddUntilStep("restart completed", () => getCurrentPlayer() != null && getCurrentPlayer() != previousPlayer); + AddStep("store previous player", () => previousPlayer = getCurrentPlayer()); + + AddUntilStep("skip button visible", checkSkipButtonVisible); + + AddStep("press quick retry key", () => InputManager.PressKey(Key.Tilde)); + AddUntilStep("restart completed", () => getCurrentPlayer() != null && getCurrentPlayer() != previousPlayer); + AddStep("release quick retry key", () => InputManager.ReleaseKey(Key.Tilde)); + + AddUntilStep("wait for load", () => getCurrentPlayer()?.LoadedBeatmapSuccessfully == true); + + AddUntilStep("time reached zero", () => getCurrentPlayer()?.GameplayClockContainer.CurrentTime > 0); + AddUntilStep("skip button not visible", () => !checkSkipButtonVisible()); + } + private EpilepsyWarning getWarning() => loader.ChildrenOfType().SingleOrDefault(); private class TestPlayerLoader : PlayerLoader diff --git a/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs b/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs index 91fa09b414..23c1eda7f7 100644 --- a/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs +++ b/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Mods protected override TestPlayer CreateModPlayer(Ruleset ruleset) { var player = base.CreateModPlayer(ruleset); - player.RestartRequested = () => restartRequested = true; + player.RestartRequested = _ => restartRequested = true; return player; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 51b2042d1b..6827ff04d3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Play /// protected virtual bool PauseOnFocusLost => true; - public Action RestartRequested; + public Action RestartRequested; private bool isRestarting; @@ -267,7 +267,7 @@ namespace osu.Game.Screens.Play FailOverlay = new FailOverlay { SaveReplay = prepareAndImportScore, - OnRetry = Restart, + OnRetry = () => Restart(), OnQuit = () => PerformExit(true), }, new HotkeyExitOverlay @@ -294,7 +294,7 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; fadeOut(true); - Restart(); + Restart(true); }, }); } @@ -371,6 +371,9 @@ namespace osu.Game.Screens.Play IsBreakTime.BindTo(breakTracker.IsBreakTime); IsBreakTime.BindValueChanged(onBreakTimeChanged, true); + + if (Configuration.AutomaticallySkipIntro) + skipIntroOverlay.SkipWhenReady(); } protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); @@ -441,7 +444,7 @@ namespace osu.Game.Screens.Play { OnResume = Resume, Retries = RestartCount, - OnRetry = Restart, + OnRetry = () => Restart(), OnQuit = () => PerformExit(true), }, }, @@ -648,7 +651,8 @@ namespace osu.Game.Screens.Play /// Restart gameplay via a parent . /// This can be called from a child screen in order to trigger the restart process. /// - public void Restart() + /// Whether a quick restart was requested (skipping intro etc.). + public void Restart(bool quickRestart = false) { if (!Configuration.AllowRestart) return; @@ -660,7 +664,7 @@ namespace osu.Game.Screens.Play musicController.Stop(); sampleRestart?.Play(); - RestartRequested?.Invoke(); + RestartRequested?.Invoke(quickRestart); PerformExit(false); } @@ -840,7 +844,7 @@ namespace osu.Game.Screens.Play failAnimationLayer.Start(); if (GameplayState.Mods.OfType().Any(m => m.RestartOnFail)) - Restart(); + Restart(true); return true; } diff --git a/osu.Game/Screens/Play/PlayerConfiguration.cs b/osu.Game/Screens/Play/PlayerConfiguration.cs index d11825baee..b1b0e01d80 100644 --- a/osu.Game/Screens/Play/PlayerConfiguration.cs +++ b/osu.Game/Screens/Play/PlayerConfiguration.cs @@ -31,5 +31,10 @@ namespace osu.Game.Screens.Play /// Whether the player should be allowed to skip intros/outros, advancing to the start of gameplay or the end of a storyboard. /// public bool AllowSkipping { get; set; } = true; + + /// + /// Whether the intro should be skipped by default. + /// + public bool AutomaticallySkipIntro { get; set; } } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 674490d595..e6bd1367ef 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -123,6 +123,8 @@ namespace osu.Game.Screens.Play private EpilepsyWarning? epilepsyWarning; + private bool quickRestart; + [Resolved(CanBeNull = true)] private INotificationOverlay? notificationOverlay { get; set; } @@ -361,6 +363,7 @@ namespace osu.Game.Screens.Play return; CurrentPlayer = createPlayer(); + CurrentPlayer.Configuration.AutomaticallySkipIntro = quickRestart; CurrentPlayer.RestartCount = restartCount++; CurrentPlayer.RestartRequested = restartRequested; @@ -375,8 +378,9 @@ namespace osu.Game.Screens.Play { } - private void restartRequested() + private void restartRequested(bool quickRestartRequested) { + quickRestart = quickRestartRequested; hideOverlays = true; ValidForResume = true; } diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 687705ff1b..5c9a706549 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -39,10 +39,13 @@ namespace osu.Game.Screens.Play private double displayTime; private bool isClickable; + private bool skipQueued; [Resolved] private IGameplayClock gameplayClock { get; set; } + internal bool IsButtonVisible => fadeContainer.State == Visibility.Visible && buttonContainer.State.Value == Visibility.Visible; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; /// @@ -123,6 +126,20 @@ namespace osu.Game.Screens.Play displayTime = gameplayClock.CurrentTime; fadeContainer.TriggerShow(); + + if (skipQueued) + { + Scheduler.AddDelayed(() => button.TriggerClick(), 200); + skipQueued = false; + } + } + + public void SkipWhenReady() + { + if (IsLoaded) + button.TriggerClick(); + else + skipQueued = true; } protected override void Update() diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index c530febcae..226216b0f0 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -177,7 +177,7 @@ namespace osu.Game.Screens.Ranking { if (!this.IsCurrentScreen()) return; - player?.Restart(); + player?.Restart(true); }, }); }